diff options
57 files changed, 1297 insertions, 306 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index adb4eaca2d11..4aa820a605b2 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1604,6 +1604,7 @@ package android { field public static final int switchTextOff = 16843628; // 0x101036c field public static final int switchTextOn = 16843627; // 0x101036b field public static final int syncable = 16842777; // 0x1010019 + field @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") public static final int systemUserOnly; field public static final int tabStripEnabled = 16843453; // 0x10102bd field public static final int tabStripLeft = 16843451; // 0x10102bb field public static final int tabStripRight = 16843452; // 0x10102bc @@ -52463,9 +52464,9 @@ package android.view { field protected static final int[] PRESSED_STATE_SET; field protected static final int[] PRESSED_WINDOW_FOCUSED_STATE_SET; field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_DEFAULT = 0.0f; - field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120.0f; - field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30.0f; - field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60.0f; + field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4.0f; + field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2.0f; + field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3.0f; field @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1.0f; field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION; field public static final android.util.Property<android.view.View,java.lang.Float> ROTATION_X; @@ -57119,7 +57120,7 @@ package android.webkit { method public abstract boolean getBuiltInZoomControls(); method public abstract int getCacheMode(); method public abstract String getCursiveFontFamily(); - method @Deprecated public abstract boolean getDatabaseEnabled(); + method public abstract boolean getDatabaseEnabled(); method @Deprecated public abstract String getDatabasePath(); method public abstract int getDefaultFixedFontSize(); method public abstract int getDefaultFontSize(); @@ -57165,7 +57166,7 @@ package android.webkit { method public abstract void setBuiltInZoomControls(boolean); method public abstract void setCacheMode(int); method public abstract void setCursiveFontFamily(String); - method @Deprecated public abstract void setDatabaseEnabled(boolean); + method public abstract void setDatabaseEnabled(boolean); method @Deprecated public abstract void setDatabasePath(String); method public abstract void setDefaultFixedFontSize(int); method public abstract void setDefaultFontSize(int); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 949e2ba07a18..e288b42f7ec7 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -6834,23 +6834,24 @@ public final class ActivityThread extends ClientTransactionHandler PackageManager.GET_SHARED_LIBRARY_FILES, UserHandle.myUserId()); - if (mActivities.size() > 0) { - for (ActivityClientRecord ar : mActivities.values()) { - if (ar.activityInfo.applicationInfo.packageName - .equals(packageName)) { - ar.activityInfo.applicationInfo = aInfo; - ar.packageInfo = pkgInfo; + if (aInfo != null) { + if (mActivities.size() > 0) { + for (ActivityClientRecord ar : mActivities.values()) { + if (ar.activityInfo.applicationInfo.packageName + .equals(packageName)) { + ar.activityInfo.applicationInfo = aInfo; + ar.packageInfo = pkgInfo; + } } } - } - final String[] oldResDirs = { pkgInfo.getResDir() }; + final String[] oldResDirs = {pkgInfo.getResDir()}; - final ArrayList<String> oldPaths = new ArrayList<>(); - LoadedApk.makePaths(this, pkgInfo.getApplicationInfo(), oldPaths); - pkgInfo.updateApplicationInfo(aInfo, oldPaths); + final ArrayList<String> oldPaths = new ArrayList<>(); + LoadedApk.makePaths( + this, pkgInfo.getApplicationInfo(), oldPaths); + pkgInfo.updateApplicationInfo(aInfo, oldPaths); - synchronized (mResourcesManager) { // Update affected Resources objects to use new ResourcesImpl mResourcesManager.appendPendingAppInfoUpdate(oldResDirs, aInfo); diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index c7a75ed5ea9c..e9b94c9f5791 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -41,6 +41,7 @@ import android.content.res.Configuration; import android.database.Cursor; import android.database.MatrixCursor; import android.database.SQLException; +import android.multiuser.Flags; import android.net.Uri; import android.os.AsyncTask; import android.os.Binder; @@ -146,6 +147,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall private boolean mExported; private boolean mNoPerms; private boolean mSingleUser; + private boolean mSystemUserOnly; private SparseBooleanArray mUsersRedirectedToOwnerForMedia = new SparseBooleanArray(); private ThreadLocal<AttributionSource> mCallingAttributionSource; @@ -377,7 +379,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall != PermissionChecker.PERMISSION_GRANTED && getContext().checkUriPermission(userUri, Binder.getCallingPid(), callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION) - != PackageManager.PERMISSION_GRANTED) { + != PackageManager.PERMISSION_GRANTED + && !deniedAccessSystemUserOnlyProvider(callingUserId, + mSystemUserOnly)) { FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION, enumCheckUriPermission, callingUid, uri.getAuthority(), type); @@ -865,6 +869,10 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall boolean checkUser(int pid, int uid, Context context) { final int callingUserId = UserHandle.getUserId(uid); + if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) { + return false; + } + if (callingUserId == context.getUserId() || mSingleUser) { return true; } @@ -987,6 +995,9 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall // last chance, check against any uri grants final int callingUserId = UserHandle.getUserId(uid); + if (deniedAccessSystemUserOnlyProvider(callingUserId, mSystemUserOnly)) { + return PermissionChecker.PERMISSION_HARD_DENIED; + } final Uri userUri = (mSingleUser && !UserHandle.isSameUser(mMyUid, uid)) ? maybeAddUserId(uri, callingUserId) : uri; if (context.checkUriPermission(userUri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) @@ -2623,6 +2634,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall setPathPermissions(info.pathPermissions); mExported = info.exported; mSingleUser = (info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0; + mSystemUserOnly = (info.flags & ProviderInfo.FLAG_SYSTEM_USER_ONLY) != 0; setAuthorities(info.authority); } if (Build.IS_DEBUGGABLE) { @@ -2756,6 +2768,11 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall String auth = uri.getAuthority(); if (!mSingleUser) { int userId = getUserIdFromAuthority(auth, UserHandle.USER_CURRENT); + if (deniedAccessSystemUserOnlyProvider(mContext.getUserId(), + mSystemUserOnly)) { + throw new SecurityException("Trying to query a SYSTEM user only content" + + " provider from user:" + mContext.getUserId()); + } if (userId != UserHandle.USER_CURRENT && userId != mContext.getUserId() // Since userId specified in content uri, the provider userId would be @@ -2929,4 +2946,16 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall Trace.traceBegin(traceTag, methodName + subInfo); } } + /** + * Return true if access to content provider is denied because it's a SYSTEM user only + * provider and the calling user is not the SYSTEM user. + * + * @param callingUserId UserId of the caller accessing the content provider. + * @param systemUserOnly true when the content provider is only available for the SYSTEM user. + */ + private static boolean deniedAccessSystemUserOnlyProvider(int callingUserId, + boolean systemUserOnly) { + return Flags.enableSystemUserOnlyForServicesAndProviders() + && (callingUserId != UserHandle.USER_SYSTEM && systemUserOnly); + } } diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index 9e553dbfb719..de33fa8b2328 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -89,6 +89,15 @@ public final class ProviderInfo extends ComponentInfo public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000; /** + * Bit in {@link #flags}: If set, this provider will only be available + * for the system user. + * Set from the android.R.attr#systemUserOnly attribute. + * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY} + * @hide + */ + public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY; + + /** * Bit in {@link #flags}: If set, a single instance of the provider will * run for all users on the device. Set from the * {@link android.R.attr#singleUser} attribute. diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index ae46c027505e..2b378b1f09d0 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -101,6 +101,14 @@ public class ServiceInfo extends ComponentInfo public static final int FLAG_VISIBLE_TO_INSTANT_APP = 0x100000; /** + * @hide Bit in {@link #flags}: If set, this service will only be available + * for the system user. + * Set from the android.R.attr#systemUserOnly attribute. + * In Sync with {@link ActivityInfo#FLAG_SYSTEM_USER_ONLY} + */ + public static final int FLAG_SYSTEM_USER_ONLY = ActivityInfo.FLAG_SYSTEM_USER_ONLY; + + /** * Bit in {@link #flags}: If set, a single instance of the service will * run for all users on the device. Set from the * {@link android.R.attr#singleUser} attribute. diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 5bfc012844f8..9644d8095a4d 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -84,4 +84,11 @@ flag { namespace: "profile_experiences" description: "Enable auto-locking private space on device restarts" bug: "296993385" -}
\ No newline at end of file +} +flag { + name: "enable_system_user_only_for_services_and_providers" + namespace: "multiuser" + description: "Enable systemUserOnly manifest attribute for services and providers." + bug: "302354856" + is_fixed_read_only: true +} diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c98d1d7ecaea..0f83d58d418d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5546,11 +5546,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) public static final float REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE = -1; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) - public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -30; + public static final float REQUESTED_FRAME_RATE_CATEGORY_LOW = -2; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) - public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -60; + public static final float REQUESTED_FRAME_RATE_CATEGORY_NORMAL = -3; @FlaggedApi(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) - public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -120; + public static final float REQUESTED_FRAME_RATE_CATEGORY_HIGH = -4; /** * Simple constructor to use when creating a view from code. diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index d12eda35c745..14c53489ba3a 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -1203,11 +1203,7 @@ public abstract class WebSettings { * changes to this setting after that point. * * @param flag {@code true} if the WebView should use the database storage API - * @deprecated WebSQL is deprecated and this method will become a no-op on all - * Android versions once support is removed in Chromium. See - * https://developer.chrome.com/blog/deprecating-web-sql for more information. */ - @Deprecated public abstract void setDatabaseEnabled(boolean flag); /** @@ -1240,11 +1236,7 @@ public abstract class WebSettings { * * @return {@code true} if the database storage API is enabled * @see #setDatabaseEnabled - * @deprecated WebSQL is deprecated and this method will become a no-op on all - * Android versions once support is removed in Chromium. See - * https://developer.chrome.com/blog/deprecating-web-sql for more information. */ - @Deprecated public abstract boolean getDatabaseEnabled(); /** diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java index 5d82d0469d56..12aff1c6669f 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java @@ -29,6 +29,7 @@ import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.multiuser.Flags; import android.os.Build; import android.os.PatternMatcher; import android.util.Slog; @@ -126,6 +127,10 @@ public class ParsedProviderUtils { .setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SINGLE_USER, R.styleable.AndroidManifestProvider_singleUser, sa)); + if (Flags.enableSystemUserOnlyForServicesAndProviders()) { + provider.setFlags(provider.getFlags() | flag(ProviderInfo.FLAG_SYSTEM_USER_ONLY, + R.styleable.AndroidManifestProvider_systemUserOnly, sa)); + } visibleToEphemeral = sa.getBoolean( R.styleable.AndroidManifestProvider_visibleToInstantApps, false); if (visibleToEphemeral) { diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java index a1dd19a3bc90..4ac542f84226 100644 --- a/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java +++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java @@ -29,6 +29,7 @@ import android.content.pm.parsing.result.ParseResult; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; +import android.multiuser.Flags; import android.os.Build; import com.android.internal.R; @@ -105,6 +106,11 @@ public class ParsedServiceUtils { | flag(ServiceInfo.FLAG_SINGLE_USER, R.styleable.AndroidManifestService_singleUser, sa))); + if (Flags.enableSystemUserOnlyForServicesAndProviders()) { + service.setFlags(service.getFlags() | flag(ServiceInfo.FLAG_SYSTEM_USER_ONLY, + R.styleable.AndroidManifestService_systemUserOnly, sa)); + } + visibleToEphemeral = sa.getBoolean( R.styleable.AndroidManifestService_visibleToInstantApps, false); if (visibleToEphemeral) { diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 29086a452ee6..35276bf8ead2 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -506,6 +506,12 @@ receivers, and providers; it can not be used with activities. --> <attr name="singleUser" format="boolean" /> + <!-- If set to true, only a single instance of this component will + run and be available for the SYSTEM user. Non SYSTEM users will not be + allowed to access the component if this flag is enabled. + This flag can be used with services, receivers, providers and activities. --> + <attr name="systemUserOnly" format="boolean" /> + <!-- Specify a specific process that the associated code is to run in. Use with the application tag (to supply a default process for all application components), or with the activity, receiver, service, @@ -2865,6 +2871,7 @@ Context.createAttributionContext() using the first attribution tag contained here. --> <attr name="attributionTags" /> + <attr name="systemUserOnly" format="boolean" /> </declare-styleable> <!-- Attributes that can be supplied in an AndroidManifest.xml @@ -3023,6 +3030,7 @@ ignored when the process is bound into a shared isolated process by a client. --> <attr name="allowSharedIsolatedProcess" format="boolean" /> + <attr name="systemUserOnly" format="boolean" /> </declare-styleable> <!-- @hide The <code>apex-system-service</code> tag declares an apex system service @@ -3150,7 +3158,7 @@ <attr name="uiOptions" /> <attr name="parentActivityName" /> <attr name="singleUser" /> - <!-- @hide This broadcast receiver or activity will only receive broadcasts for the + <!-- This broadcast receiver or activity will only receive broadcasts for the system user--> <attr name="systemUserOnly" format="boolean" /> <attr name="persistableMode" /> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 7d2288599841..b8fc052a2fa9 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -121,6 +121,8 @@ <public name="adServiceTypes" /> <!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") --> <public name="featureFlag"/> + <!-- @FlaggedApi("android.multiuser.enable_system_user_only_for_services_and_providers") --> + <public name="systemUserOnly"/> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 97b1ec713c5b..2de305f8e925 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -639,4 +639,8 @@ applications that come with the platform <permission name="com.android.voicemail.permission.READ_VOICEMAIL"/> <permission name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"/> </privapp-permissions> + + <privapp-permissions package="com.android.devicediagnostics"> + <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> + </privapp-permissions> </permissions> diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java index d659ddd75f72..4e88b0efd3e5 100644 --- a/graphics/java/android/graphics/BaseRecordingCanvas.java +++ b/graphics/java/android/graphics/BaseRecordingCanvas.java @@ -607,7 +607,8 @@ public class BaseRecordingCanvas extends Canvas { } @Override - public final void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) { + public final void drawMesh(@NonNull Mesh mesh, @Nullable BlendMode blendMode, + @NonNull Paint paint) { if (blendMode == null) { blendMode = BlendMode.MODULATE; } diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt new file mode 100644 index 000000000000..4c76168cdeaa --- /dev/null +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleTaskViewTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 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.wm.shell.bubbles + +import android.content.ComponentName +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.wm.shell.taskview.TaskView +import com.android.wm.shell.taskview.TaskViewTaskController + +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors.directExecutor +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +@SmallTest +@RunWith(AndroidJUnit4::class) +class BubbleTaskViewTest { + + private lateinit var bubbleTaskView: BubbleTaskView + private val context = ApplicationProvider.getApplicationContext<Context>() + + @Before + fun setUp() { + val taskView = TaskView(context, mock<TaskViewTaskController>()) + bubbleTaskView = BubbleTaskView(taskView, directExecutor()) + } + + @Test + fun onTaskCreated_updatesState() { + val componentName = ComponentName(context, "TestClass") + bubbleTaskView.listener.onTaskCreated(123, componentName) + + assertThat(bubbleTaskView.taskId).isEqualTo(123) + assertThat(bubbleTaskView.componentName).isEqualTo(componentName) + assertThat(bubbleTaskView.isCreated).isTrue() + } + + @Test + fun onTaskCreated_callsDelegateListener() { + var actualTaskId = -1 + var actualComponentName: ComponentName? = null + val delegateListener = object : TaskView.Listener { + override fun onTaskCreated(taskId: Int, name: ComponentName) { + actualTaskId = taskId + actualComponentName = name + } + } + bubbleTaskView.delegateListener = delegateListener + + val componentName = ComponentName(context, "TestClass") + bubbleTaskView.listener.onTaskCreated(123, componentName) + + assertThat(actualTaskId).isEqualTo(123) + assertThat(actualComponentName).isEqualTo(componentName) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index f3fe895bf9b4..9f7d0ac9bafe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -74,7 +74,6 @@ import com.android.wm.shell.R; import com.android.wm.shell.common.AlphaOptimizedButton; import com.android.wm.shell.common.TriangleShape; import com.android.wm.shell.taskview.TaskView; -import com.android.wm.shell.taskview.TaskViewTaskController; import java.io.PrintWriter; @@ -146,7 +145,6 @@ public class BubbleExpandedView extends LinearLayout { private AlphaOptimizedButton mManageButton; private TaskView mTaskView; - private TaskViewTaskController mTaskViewTaskController; private BubbleOverflowContainerView mOverflowView; private int mTaskId = INVALID_TASK_ID; @@ -434,7 +432,8 @@ public class BubbleExpandedView extends LinearLayout { * Initialize {@link BubbleController} and {@link BubbleStackView} here, this method must need * to be called after view inflate. */ - void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow) { + void initialize(BubbleController controller, BubbleStackView stackView, boolean isOverflow, + @Nullable BubbleTaskView bubbleTaskView) { mController = controller; mStackView = stackView; mIsOverflow = isOverflow; @@ -451,18 +450,22 @@ public class BubbleExpandedView extends LinearLayout { bringChildToFront(mOverflowView); mManageButton.setVisibility(GONE); } else { - mTaskViewTaskController = new TaskViewTaskController(mContext, - mController.getTaskOrganizer(), - mController.getTaskViewTransitions(), mController.getSyncTransactionQueue()); - mTaskView = new TaskView(mContext, mTaskViewTaskController); - mTaskView.setListener(mController.getMainExecutor(), mTaskViewListener); + mTaskView = bubbleTaskView.getTaskView(); + bubbleTaskView.setDelegateListener(mTaskViewListener); // set a fixed width so it is not recalculated as part of a rotation. the width will be // updated manually after the rotation. FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(getContentWidth(), MATCH_PARENT); + if (mTaskView.getParent() != null) { + ((ViewGroup) mTaskView.getParent()).removeView(mTaskView); + } mExpandedViewContainer.addView(mTaskView, lp); bringChildToFront(mTaskView); + if (bubbleTaskView.isCreated()) { + mTaskViewListener.onTaskCreated( + bubbleTaskView.getTaskId(), bubbleTaskView.getComponentName()); + } } } @@ -876,7 +879,7 @@ public class BubbleExpandedView extends LinearLayout { return; } boolean isNew = mBubble == null || didBackingContentChange(bubble); - if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) { + if (isNew || bubble.getKey().equals(mBubble.getKey())) { mBubble = bubble; mManageButton.setContentDescription(getResources().getString( R.string.bubbles_settings_button_description, bubble.getAppName())); @@ -1107,7 +1110,8 @@ public class BubbleExpandedView extends LinearLayout { * has been removed. * * If this view should be reused after this method is called, then - * {@link #initialize(BubbleController, BubbleStackView, boolean)} must be invoked first. + * {@link #initialize(BubbleController, BubbleStackView, boolean, BubbleTaskView)} + * must be invoked first. */ public void cleanUpExpandedState() { if (DEBUG_BUBBLE_EXPANDED_VIEW) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index 22e836aacfc5..e5d9acedc903 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -29,6 +29,7 @@ import android.util.PathParser import android.view.LayoutInflater import android.view.View.VISIBLE import android.widget.FrameLayout +import androidx.core.content.ContextCompat import com.android.launcher3.icons.BubbleIconFactory import com.android.wm.shell.R import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView @@ -57,10 +58,16 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl /** Call before use and again if cleanUpExpandedState was called. */ fun initialize(controller: BubbleController, forBubbleBar: Boolean) { if (forBubbleBar) { - createBubbleBarExpandedView().initialize(controller, true /* isOverflow */) + createBubbleBarExpandedView() + .initialize(controller, /* isOverflow= */ true, /* bubbleTaskView= */ null) } else { createExpandedView() - .initialize(controller, controller.stackView, true /* isOverflow */) + .initialize( + controller, + controller.stackView, + /* isOverflow= */ true, + /* bubbleTaskView= */ null + ) } } @@ -113,7 +120,10 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl context, res.getDimensionPixelSize(R.dimen.bubble_size), res.getDimensionPixelSize(R.dimen.bubble_badge_size), - res.getColor(com.android.launcher3.icons.R.color.important_conversation), + ContextCompat.getColor( + context, + com.android.launcher3.icons.R.color.important_conversation + ), res.getDimensionPixelSize(com.android.internal.R.dimen.importance_ring_stroke_width) ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt new file mode 100644 index 000000000000..2fcd133c7b20 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskView.kt @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 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.wm.shell.bubbles + +import android.app.ActivityTaskManager.INVALID_TASK_ID +import android.content.ComponentName +import androidx.annotation.VisibleForTesting +import com.android.wm.shell.taskview.TaskView +import java.util.concurrent.Executor + +/** + * A wrapper class around [TaskView] for bubble expanded views. + * + * [delegateListener] allows callers to change listeners after a task has been created. + */ +class BubbleTaskView(val taskView: TaskView, executor: Executor) { + + /** Whether the task is already created. */ + var isCreated = false + private set + + /** The task id. */ + var taskId = INVALID_TASK_ID + private set + + /** The component name of the application running in the task. */ + var componentName: ComponentName? = null + private set + + /** [TaskView.Listener] for users of this class. */ + var delegateListener: TaskView.Listener? = null + + /** A [TaskView.Listener] that delegates to [delegateListener]. */ + @get:VisibleForTesting + val listener = object : TaskView.Listener { + override fun onInitialized() { + delegateListener?.onInitialized() + } + + override fun onReleased() { + delegateListener?.onReleased() + } + + override fun onTaskCreated(taskId: Int, name: ComponentName) { + delegateListener?.onTaskCreated(taskId, name) + this@BubbleTaskView.taskId = taskId + isCreated = true + componentName = name + } + + override fun onTaskVisibilityChanged(taskId: Int, visible: Boolean) { + delegateListener?.onTaskVisibilityChanged(taskId, visible) + } + + override fun onTaskRemovalStarted(taskId: Int) { + delegateListener?.onTaskRemovalStarted(taskId) + } + + override fun onBackPressedOnTaskRoot(taskId: Int) { + delegateListener?.onBackPressedOnTaskRoot(taskId) + } + } + + init { + taskView.setListener(executor, listener) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index f6c382fb5b3d..5855a81333d4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -35,10 +35,7 @@ import android.view.View; import androidx.annotation.Nullable; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.taskview.TaskView; -import com.android.wm.shell.taskview.TaskViewTaskController; /** * Handles creating and updating the {@link TaskView} associated with a {@link Bubble}. @@ -65,7 +62,6 @@ public class BubbleTaskViewHelper { private final Context mContext; private final BubbleController mController; - private final @ShellMainThread ShellExecutor mMainExecutor; private final BubbleTaskViewHelper.Listener mListener; private final View mParentView; @@ -73,7 +69,6 @@ public class BubbleTaskViewHelper { private Bubble mBubble; @Nullable private PendingIntent mPendingIntent; - private TaskViewTaskController mTaskViewTaskController; @Nullable private TaskView mTaskView; private int mTaskId = INVALID_TASK_ID; @@ -204,17 +199,18 @@ public class BubbleTaskViewHelper { public BubbleTaskViewHelper(Context context, BubbleController controller, BubbleTaskViewHelper.Listener listener, + BubbleTaskView bubbleTaskView, View parent) { mContext = context; mController = controller; - mMainExecutor = mController.getMainExecutor(); mListener = listener; mParentView = parent; - mTaskViewTaskController = new TaskViewTaskController(mContext, - mController.getTaskOrganizer(), - mController.getTaskViewTransitions(), mController.getSyncTransactionQueue()); - mTaskView = new TaskView(mContext, mTaskViewTaskController); - mTaskView.setListener(mMainExecutor, mTaskViewListener); + mTaskView = bubbleTaskView.getTaskView(); + bubbleTaskView.setDelegateListener(mTaskViewListener); + if (bubbleTaskView.isCreated()) { + mTaskId = bubbleTaskView.getTaskId(); + mListener.onTaskCreated(); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index bb30c5eeebcf..c3d899e7dac7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -46,6 +46,8 @@ import com.android.launcher3.icons.BubbleIconFactory; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView; import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; +import com.android.wm.shell.taskview.TaskView; +import com.android.wm.shell.taskview.TaskViewTaskController; import java.lang.ref.WeakReference; import java.util.Objects; @@ -173,10 +175,12 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask BubbleViewInfo info = new BubbleViewInfo(); if (!skipInflation && !b.isInflated()) { + BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller); LayoutInflater inflater = LayoutInflater.from(c); info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate( R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */); - info.bubbleBarExpandedView.initialize(controller, false /* isOverflow */); + info.bubbleBarExpandedView.initialize( + controller, false /* isOverflow */, bubbleTaskView); } if (!populateCommonInfo(info, c, b, iconFactory)) { @@ -201,9 +205,11 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask R.layout.bubble_view, stackView, false /* attachToRoot */); info.imageView.initialize(controller.getPositioner()); + BubbleTaskView bubbleTaskView = createBubbleTaskView(c, controller); info.expandedView = (BubbleExpandedView) inflater.inflate( R.layout.bubble_expanded_view, stackView, false /* attachToRoot */); - info.expandedView.initialize(controller, stackView, false /* isOverflow */); + info.expandedView.initialize( + controller, stackView, false /* isOverflow */, bubbleTaskView); } if (!populateCommonInfo(info, c, b, iconFactory)) { @@ -219,6 +225,15 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask } return info; } + + private static BubbleTaskView createBubbleTaskView( + Context context, BubbleController controller) { + TaskViewTaskController taskViewTaskController = new TaskViewTaskController(context, + controller.getTaskOrganizer(), + controller.getTaskViewTransitions(), controller.getSyncTransactionQueue()); + TaskView taskView = new TaskView(context, taskViewTaskController); + return new BubbleTaskView(taskView, controller.getMainExecutor()); + } } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 66c0c9640477..3cf23ac114ee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -16,6 +16,8 @@ package com.android.wm.shell.bubbles.bar; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; + import android.annotation.Nullable; import android.app.ActivityManager; import android.content.Context; @@ -27,6 +29,7 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.widget.FrameLayout; @@ -35,6 +38,7 @@ import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleController; import com.android.wm.shell.bubbles.BubbleOverflowContainerView; +import com.android.wm.shell.bubbles.BubbleTaskView; import com.android.wm.shell.bubbles.BubbleTaskViewHelper; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.taskview.TaskView; @@ -130,7 +134,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView } /** Set the BubbleController on the view, must be called before doing anything else. */ - public void initialize(BubbleController controller, boolean isOverflow) { + public void initialize(BubbleController controller, boolean isOverflow, + @Nullable BubbleTaskView bubbleTaskView) { mController = controller; mIsOverflow = isOverflow; @@ -140,14 +145,19 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView mOverflowView.setBubbleController(mController); addView(mOverflowView); } else { - + mTaskView = bubbleTaskView.getTaskView(); mBubbleTaskViewHelper = new BubbleTaskViewHelper(mContext, mController, - /* listener= */ this, + /* listener= */ this, bubbleTaskView, /* viewParent= */ this); - mTaskView = mBubbleTaskViewHelper.getTaskView(); - addView(mTaskView); + if (mTaskView.getParent() != null) { + ((ViewGroup) mTaskView.getParent()).removeView(mTaskView); + } + FrameLayout.LayoutParams lp = + new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); + addView(mTaskView, lp); mTaskView.setEnableSurfaceClipping(true); mTaskView.setCornerRadius(mCornerRadius); + mTaskView.setVisibility(VISIBLE); // Handle view needs to draw on top of task view. bringChildToFront(mHandleView); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index bba9c9764eee..f84107e8792c 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -111,7 +111,11 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& : PointerController( policy, looper, spriteController, enabled, [](const sp<android::gui::WindowInfosListener>& listener) { - SurfaceComposerClient::getDefault()->addWindowInfosListener(listener); + auto initialInfo = std::make_pair(std::vector<android::gui::WindowInfo>{}, + std::vector<android::gui::DisplayInfo>{}); + SurfaceComposerClient::getDefault()->addWindowInfosListener(listener, + &initialInfo); + return initialInfo.second; }, [](const sp<android::gui::WindowInfosListener>& listener) { SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener); @@ -119,8 +123,9 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, SpriteController& spriteController, - bool enabled, WindowListenerConsumer registerListener, - WindowListenerConsumer unregisterListener) + bool enabled, + const WindowListenerRegisterConsumer& registerListener, + WindowListenerUnregisterConsumer unregisterListener) : mEnabled(enabled), mContext(policy, looper, spriteController, *this), mCursorController(mContext), @@ -128,7 +133,8 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& mUnregisterWindowInfosListener(std::move(unregisterListener)) { std::scoped_lock lock(getLock()); mLocked.presentation = Presentation::SPOT; - registerListener(mDisplayInfoListener); + const auto& initialDisplayInfos = registerListener(mDisplayInfoListener); + onDisplayInfosChangedLocked(initialDisplayInfos); } PointerController::~PointerController() { diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index a8b963367f4c..6ee5707622ca 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -79,14 +79,16 @@ public: std::string dump() override; protected: - using WindowListenerConsumer = + using WindowListenerRegisterConsumer = std::function<std::vector<gui::DisplayInfo>( + const sp<android::gui::WindowInfosListener>&)>; + using WindowListenerUnregisterConsumer = std::function<void(const sp<android::gui::WindowInfosListener>&)>; // Constructor used to test WindowInfosListener registration. PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, SpriteController& spriteController, bool enabled, - WindowListenerConsumer registerListener, - WindowListenerConsumer unregisterListener); + const WindowListenerRegisterConsumer& registerListener, + WindowListenerUnregisterConsumer unregisterListener); PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, SpriteController& spriteController, bool enabled); @@ -129,7 +131,7 @@ private: }; sp<DisplayInfoListener> mDisplayInfoListener; - const WindowListenerConsumer mUnregisterWindowInfosListener; + const WindowListenerUnregisterConsumer mUnregisterWindowInfosListener; const ui::Transform& getTransformForDisplayLocked(int displayId) const REQUIRES(getLock()); diff --git a/libs/input/TouchSpotController.cpp b/libs/input/TouchSpotController.cpp index b8de919fbd8c..99952aa14904 100644 --- a/libs/input/TouchSpotController.cpp +++ b/libs/input/TouchSpotController.cpp @@ -93,7 +93,7 @@ void TouchSpotController::setSpots(const PointerCoords* spotCoords, const uint32 const PointerCoords& c = spotCoords[spotIdToIndex[id]]; ALOGD(" spot %d: position=(%0.3f, %0.3f), pressure=%0.3f, displayId=%" PRId32 ".", id, c.getAxisValue(AMOTION_EVENT_AXIS_X), c.getAxisValue(AMOTION_EVENT_AXIS_Y), - c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), displayId); + c.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), mDisplayId); } #endif diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index adfa91e96ebb..a1bb5b3f1cc4 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -160,9 +160,11 @@ public: : PointerController( policy, looper, spriteController, /*enabled=*/true, - [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { + [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) + -> std::vector<gui::DisplayInfo> { // Register listener registeredListener = listener; + return {}; }, [®isteredListener](const sp<android::gui::WindowInfosListener>& listener) { // Unregister listener diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 5cebd96249b6..50caf17f71dd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2507,8 +2507,18 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, String message = ""; switch (msg.what) { case SHOW: - message = "SHOW"; - handleShow((Bundle) msg.obj); + // There is a potential race condition when SysUI starts up. CentralSurfaces + // must invoke #registerCentralSurfaces on this class before any messages can be + // processed. If this happens, repost the message with a small delay and try + // again. + if (mCentralSurfaces == null) { + message = "DELAYING SHOW"; + Message newMsg = mHandler.obtainMessage(SHOW, (Bundle) msg.obj); + mHandler.sendMessageDelayed(newMsg, 100); + } else { + message = "SHOW"; + handleShow((Bundle) msg.obj); + } break; case HIDE: message = "HIDE"; @@ -2595,8 +2605,18 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Trace.endSection(); break; case SYSTEM_READY: - message = "SYSTEM_READY"; - handleSystemReady(); + // There is a potential race condition when SysUI starts up. CentralSurfaces + // must invoke #registerCentralSurfaces on this class before any messages can be + // processed. If this happens, repost the message with a small delay and try + // again. + if (mCentralSurfaces == null) { + message = "DELAYING SYSTEM_READY"; + Message newMsg = mHandler.obtainMessage(SYSTEM_READY); + mHandler.sendMessageDelayed(newMsg, 100); + } else { + message = "SYSTEM_READY"; + handleSystemReady(); + } break; } Log.d(TAG, "KeyguardViewMediator queue processing message: " + message); diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 3dd3e0762c7c..8d6493f9a278 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -24,6 +24,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel import com.android.systemui.lifecycle.repeatWhenAttached @@ -53,7 +54,8 @@ class KeyguardBlueprintViewBinder { } // Apply transition. - if (prevBluePrint != null && prevBluePrint != blueprint) { + if (!keyguardBottomAreaRefactor() && prevBluePrint != null && + prevBluePrint != blueprint) { TransitionManager.beginDelayedTransition( constraintLayout, BaseBlueprintTransition() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index 909cff37b67d..e5982428fe14 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -542,7 +542,19 @@ public final class KeyboardShortcutListSearch { new ShortcutKeyGroupMultiMappingInfo( context.getString(R.string.group_system_access_google_assistant), Arrays.asList( - Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))) + Pair.create(KeyEvent.KEYCODE_A, KeyEvent.META_META_ON))), + /* Lock screen: Meta + L */ + new ShortcutKeyGroupMultiMappingInfo( + context.getString(R.string.group_system_lock_screen), + Arrays.asList( + Pair.create(KeyEvent.KEYCODE_L, KeyEvent.META_META_ON))), + /* Pull up Notes app for quick memo: Meta + Ctrl + N */ + new ShortcutKeyGroupMultiMappingInfo( + context.getString(R.string.group_system_quick_memo), + Arrays.asList( + Pair.create( + KeyEvent.KEYCODE_N, + KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON))) ); for (ShortcutKeyGroupMultiMappingInfo info : infoList) { systemGroup.addItem(info.getShortcutMultiMappingInfo()); @@ -584,11 +596,17 @@ public final class KeyboardShortcutListSearch { new ArrayList<>()); // System multitasking shortcuts: + // Enter Split screen with current app to RHS: Meta + Ctrl + Right arrow + // Enter Split screen with current app to LHS: Meta + Ctrl + Left arrow // Switch from Split screen to full screen: Meta + Ctrl + Up arrow String[] shortcutLabels = { + context.getString(R.string.system_multitasking_rhs), + context.getString(R.string.system_multitasking_lhs), context.getString(R.string.system_multitasking_full_screen), }; int[] keyCodes = { + KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_UP, }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt index fe49c0791370..6b303263d4b0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor import android.content.Context +import com.android.internal.telephony.flags.Flags import com.android.settingslib.SignalIcon.MobileIconGroup import com.android.settingslib.graph.SignalDrawable import com.android.settingslib.mobile.MobileIconCarrierIdOverrides @@ -32,14 +33,18 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIc import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.DefaultIcon import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel.OverriddenIcon import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel +import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -79,6 +84,9 @@ interface MobileIconInteractor { /** Whether or not to show the slice attribution */ val showSliceAttribution: StateFlow<Boolean> + /** True if this connection is satellite-based */ + val isNonTerrestrial: StateFlow<Boolean> + /** * Provider name for this network connection. The name can be one of 3 values: * 1. The default network name, if one is configured @@ -244,6 +252,13 @@ class MobileIconInteractorImpl( override val showSliceAttribution: StateFlow<Boolean> = connectionRepository.hasPrioritizedNetworkCapabilities + override val isNonTerrestrial: StateFlow<Boolean> = + if (Flags.carrierEnabledSatelliteFlag()) { + connectionRepository.isNonTerrestrial + } else { + MutableStateFlow(false).asStateFlow() + } + override val isRoaming: StateFlow<Boolean> = combine( connectionRepository.carrierNetworkChangeActive, @@ -313,26 +328,45 @@ class MobileIconInteractorImpl( } .stateIn(scope, SharingStarted.WhileSubscribed(), 0) - override val signalLevelIcon: StateFlow<SignalIconModel> = run { - val initial = - SignalIconModel( - level = shownLevel.value, - numberOfLevels = numberOfLevels.value, - showExclamationMark = showExclamationMark.value, - carrierNetworkChange = carrierNetworkChangeActive.value, - ) + private val cellularIcon: Flow<SignalIconModel.Cellular> = combine( + shownLevel, + numberOfLevels, + showExclamationMark, + carrierNetworkChangeActive, + ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange -> + SignalIconModel.Cellular( shownLevel, numberOfLevels, showExclamationMark, - carrierNetworkChangeActive, - ) { shownLevel, numberOfLevels, showExclamationMark, carrierNetworkChange -> - SignalIconModel( - shownLevel, - numberOfLevels, - showExclamationMark, - carrierNetworkChange, - ) + carrierNetworkChange, + ) + } + + private val satelliteIcon: Flow<SignalIconModel.Satellite> = + shownLevel.map { + SignalIconModel.Satellite( + level = it, + icon = SatelliteIconModel.fromSignalStrength(it) + ?: SatelliteIconModel.fromSignalStrength(0)!! + ) + } + + override val signalLevelIcon: StateFlow<SignalIconModel> = run { + val initial = + SignalIconModel.Cellular( + shownLevel.value, + numberOfLevels.value, + showExclamationMark.value, + carrierNetworkChangeActive.value, + ) + isNonTerrestrial + .flatMapLatest { ntn -> + if (ntn) { + satelliteIcon + } else { + cellularIcon + } } .distinctUntilChanged() .logDiffsForTable( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt index e58f08183157..d6b8fd4fec87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/model/SignalIconModel.kt @@ -17,51 +17,94 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.model import com.android.settingslib.graph.SignalDrawable +import com.android.systemui.common.shared.model.Icon import com.android.systemui.log.table.Diffable import com.android.systemui.log.table.TableRowLogger -/** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */ -data class SignalIconModel( - val level: Int, - val numberOfLevels: Int, - val showExclamationMark: Boolean, - val carrierNetworkChange: Boolean, -) : Diffable<SignalIconModel> { - // TODO(b/267767715): Can we implement [logDiffs] and [logFull] generically for data classes? +sealed interface SignalIconModel : Diffable<SignalIconModel> { + val level: Int + override fun logDiffs(prevVal: SignalIconModel, row: TableRowLogger) { - if (prevVal.level != level) { - row.logChange(COL_LEVEL, level) + logPartial(prevVal, row) + } + + override fun logFull(row: TableRowLogger) = logFully(row) + + fun logFully(row: TableRowLogger) + + fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) + + /** A model that will be consumed by [SignalDrawable] to show the mobile triangle icon. */ + data class Cellular( + override val level: Int, + val numberOfLevels: Int, + val showExclamationMark: Boolean, + val carrierNetworkChange: Boolean, + ) : SignalIconModel { + override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) { + if (prevVal !is Cellular) { + logFull(row) + } else { + if (prevVal.level != level) { + row.logChange(COL_LEVEL, level) + } + if (prevVal.numberOfLevels != numberOfLevels) { + row.logChange(COL_NUM_LEVELS, numberOfLevels) + } + if (prevVal.showExclamationMark != showExclamationMark) { + row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark) + } + if (prevVal.carrierNetworkChange != carrierNetworkChange) { + row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange) + } + } } - if (prevVal.numberOfLevels != numberOfLevels) { + + override fun logFully(row: TableRowLogger) { + row.logChange(COL_TYPE, "c") + row.logChange(COL_LEVEL, level) row.logChange(COL_NUM_LEVELS, numberOfLevels) - } - if (prevVal.showExclamationMark != showExclamationMark) { row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark) - } - if (prevVal.carrierNetworkChange != carrierNetworkChange) { row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange) } - } - override fun logFull(row: TableRowLogger) { - row.logChange(COL_LEVEL, level) - row.logChange(COL_NUM_LEVELS, numberOfLevels) - row.logChange(COL_SHOW_EXCLAMATION, showExclamationMark) - row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChange) + /** Convert this model to an [Int] consumable by [SignalDrawable]. */ + fun toSignalDrawableState(): Int = + if (carrierNetworkChange) { + SignalDrawable.getCarrierChangeState(numberOfLevels) + } else { + SignalDrawable.getState(level, numberOfLevels, showExclamationMark) + } } - /** Convert this model to an [Int] consumable by [SignalDrawable]. */ - fun toSignalDrawableState(): Int = - if (carrierNetworkChange) { - SignalDrawable.getCarrierChangeState(numberOfLevels) - } else { - SignalDrawable.getState(level, numberOfLevels, showExclamationMark) + /** + * For non-terrestrial networks, we can use a resource-backed icon instead of the + * [SignalDrawable]-backed version above + */ + data class Satellite( + override val level: Int, + val icon: Icon.Resource, + ) : SignalIconModel { + override fun logPartial(prevVal: SignalIconModel, row: TableRowLogger) { + if (prevVal !is Satellite) { + logFull(row) + } else { + if (prevVal.level != level) row.logChange(COL_LEVEL, level) + } } + override fun logFully(row: TableRowLogger) { + row.logChange("numLevels", "HELLO") + row.logChange(COL_TYPE, "s") + row.logChange(COL_LEVEL, level) + } + } + companion object { private const val COL_LEVEL = "level" private const val COL_NUM_LEVELS = "numLevels" private const val COL_SHOW_EXCLAMATION = "showExclamation" private const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChange" + private const val COL_TYPE = "type" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt index a1a5370819f8..43cb38ff0108 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/VerboseMobileViewLogger.kt @@ -59,7 +59,7 @@ constructor( str1 = parentView.getIdForLogging() int1 = subId int2 = icon.level - bool1 = icon.showExclamationMark + bool1 = if (icon is SignalIconModel.Cellular) icon.showExclamationMark else false }, { "Binder[subId=$int1, viewId=$str1] received new signal icon: " + diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt index 5475528152ed..a0c5618041f6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt @@ -38,6 +38,7 @@ import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding @@ -70,7 +71,7 @@ object MobileIconBinder { val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type) val networkTypeContainer = view.requireViewById<FrameLayout>(R.id.mobile_type_container) val iconView = view.requireViewById<ImageView>(R.id.mobile_signal) - val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) } + val mobileDrawable = SignalDrawable(view.context) val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming) val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space) val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot) @@ -138,7 +139,12 @@ object MobileIconBinder { viewModel.subscriptionId, icon, ) - mobileDrawable.level = icon.toSignalDrawableState() + if (icon is SignalIconModel.Cellular) { + iconView.setImageDrawable(mobileDrawable) + mobileDrawable.level = icon.toSignalDrawableState() + } else if (icon is SignalIconModel.Satellite) { + IconViewBinder.bind(icon.icon, iconView) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt index 60c662da6aec..eda5c44b5c2f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt @@ -33,12 +33,15 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn /** Common interface for all of the location-based mobile icon view models. */ @@ -71,7 +74,6 @@ interface MobileIconViewModelCommon { * model gets the exact same information, as well as allows us to log that unified state only once * per icon. */ -@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") @OptIn(ExperimentalCoroutinesApi::class) class MobileIconViewModel( override val subscriptionId: Int, @@ -81,6 +83,100 @@ class MobileIconViewModel( flags: FeatureFlagsClassic, scope: CoroutineScope, ) : MobileIconViewModelCommon { + private val cellProvider by lazy { + CellularIconViewModel( + subscriptionId, + iconInteractor, + airplaneModeInteractor, + constants, + flags, + scope, + ) + } + + private val satelliteProvider by lazy { + CarrierBasedSatelliteViewModelImpl( + subscriptionId, + iconInteractor, + ) + } + + /** + * Similar to repository switching, this allows us to split up the logic of satellite/cellular + * states, since they are different by nature + */ + private val vmProvider: Flow<MobileIconViewModelCommon> = + iconInteractor.isNonTerrestrial + .mapLatest { nonTerrestrial -> + if (nonTerrestrial) { + satelliteProvider + } else { + cellProvider + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), cellProvider) + + override val isVisible: StateFlow<Boolean> = + vmProvider + .flatMapLatest { it.isVisible } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + + override val icon: Flow<SignalIconModel> = vmProvider.flatMapLatest { it.icon } + + override val contentDescription: Flow<ContentDescription> = + vmProvider.flatMapLatest { it.contentDescription } + + override val roaming: Flow<Boolean> = vmProvider.flatMapLatest { it.roaming } + + override val networkTypeIcon: Flow<Icon.Resource?> = + vmProvider.flatMapLatest { it.networkTypeIcon } + + override val networkTypeBackground: StateFlow<Icon.Resource?> = + vmProvider + .flatMapLatest { it.networkTypeBackground } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + override val activityInVisible: Flow<Boolean> = + vmProvider.flatMapLatest { it.activityInVisible } + + override val activityOutVisible: Flow<Boolean> = + vmProvider.flatMapLatest { it.activityOutVisible } + + override val activityContainerVisible: Flow<Boolean> = + vmProvider.flatMapLatest { it.activityContainerVisible } +} + +/** Representation of this network when it is non-terrestrial (e.g., satellite) */ +private class CarrierBasedSatelliteViewModelImpl( + override val subscriptionId: Int, + interactor: MobileIconInteractor, +) : MobileIconViewModelCommon { + override val isVisible: StateFlow<Boolean> = MutableStateFlow(true) + override val icon: Flow<SignalIconModel> = interactor.signalLevelIcon + + override val contentDescription: Flow<ContentDescription> = + MutableStateFlow(ContentDescription.Loaded("")) + + /** These fields are not used for satellite icons currently */ + override val roaming: Flow<Boolean> = flowOf(false) + override val networkTypeIcon: Flow<Icon.Resource?> = flowOf(null) + override val networkTypeBackground: StateFlow<Icon.Resource?> = MutableStateFlow(null) + override val activityInVisible: Flow<Boolean> = flowOf(false) + override val activityOutVisible: Flow<Boolean> = flowOf(false) + override val activityContainerVisible: Flow<Boolean> = flowOf(false) +} + +/** Terrestrial (cellular) icon. */ +@Suppress("EXPERIMENTAL_IS_NOT_ENABLED") +@OptIn(ExperimentalCoroutinesApi::class) +private class CellularIconViewModel( + override val subscriptionId: Int, + iconInteractor: MobileIconInteractor, + airplaneModeInteractor: AirplaneModeInteractor, + constants: ConnectivityConstants, + flags: FeatureFlagsClassic, + scope: CoroutineScope, +) : MobileIconViewModelCommon { override val isVisible: StateFlow<Boolean> = if (!constants.hasDataCapabilities) { flowOf(false) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt index 6938d667ca81..63566ee814ab 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt @@ -28,7 +28,7 @@ object SatelliteIconModel { fun fromConnectionState( connectionState: SatelliteConnectionState, signalStrength: Int, - ): Icon? = + ): Icon.Resource? = when (connectionState) { // TODO(b/316635648): check if this should be null SatelliteConnectionState.Unknown, @@ -41,9 +41,13 @@ object SatelliteIconModel { SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength) } - private fun fromSignalStrength( + /** + * Satellite icon appropriate for when we are connected. Use [fromConnectionState] for a more + * generally correct representation. + */ + fun fromSignalStrength( signalStrength: Int, - ): Icon? = + ): Icon.Resource? = // TODO(b/316634365): these need content descriptions when (signalStrength) { // No signal diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt index ae58398753e8..352413ee568a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel import android.content.Context import android.text.Html import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -28,6 +29,7 @@ import com.android.systemui.res.R import com.android.systemui.statusbar.pipeline.airplane.data.repository.AirplaneModeRepository import com.android.systemui.statusbar.pipeline.ethernet.domain.EthernetInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.SignalIconModel import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository import com.android.systemui.statusbar.pipeline.shared.ui.model.InternetTileModel import com.android.systemui.statusbar.pipeline.shared.ui.model.SignalIcon @@ -116,14 +118,31 @@ constructor( it.signalLevelIcon, mobileDataContentName, ) { networkNameModel, signalIcon, dataContentDescription -> - val secondary = - mobileDataContentConcat(networkNameModel.name, dataContentDescription) - InternetTileModel.Active( - secondaryTitle = secondary, - icon = SignalIcon(signalIcon.toSignalDrawableState()), - stateDescription = ContentDescription.Loaded(secondary.toString()), - contentDescription = ContentDescription.Loaded(internetLabel), - ) + when (signalIcon) { + is SignalIconModel.Cellular -> { + val secondary = + mobileDataContentConcat( + networkNameModel.name, + dataContentDescription + ) + InternetTileModel.Active( + secondaryTitle = secondary, + icon = SignalIcon(signalIcon.toSignalDrawableState()), + stateDescription = ContentDescription.Loaded(secondary.toString()), + contentDescription = ContentDescription.Loaded(internetLabel), + ) + } + is SignalIconModel.Satellite -> { + val secondary = + signalIcon.icon.contentDescription.loadContentDescription(context) + InternetTileModel.Active( + secondaryTitle = secondary, + iconId = signalIcon.icon.res, + stateDescription = ContentDescription.Loaded(secondary), + contentDescription = ContentDescription.Loaded(internetLabel), + ) + } + } } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index 754a7fd81475..8a3a4342915b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -309,6 +309,28 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { @Test @TestableLooper.RunWithLooper(setAsMainLooper = true) + public void testRaceCondition_doNotRegisterCentralSurfacesImmediately() { + create(false); + + // GIVEN central surfaces is not registered with KeyguardViewMediator, but a call to enable + // keyguard comes in + mViewMediator.onSystemReady(); + mViewMediator.setKeyguardEnabled(true); + TestableLooper.get(this).processAllMessages(); + + // If this step has been reached, then system ui has not crashed. Now register + // CentralSurfaces + assertFalse(mViewMediator.isShowingAndNotOccluded()); + register(); + TestableLooper.get(this).moveTimeForward(100); + TestableLooper.get(this).processAllMessages(); + + // THEN keyguard is shown + assertTrue(mViewMediator.isShowingAndNotOccluded()); + } + + @Test + @TestableLooper.RunWithLooper(setAsMainLooper = true) public void onLockdown_showKeyguard_evenIfKeyguardIsNotEnabledExternally() { // GIVEN keyguard is not enabled and isn't showing mViewMediator.onSystemReady(); @@ -1139,6 +1161,11 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { } private void createAndStartViewMediator(boolean orderUnlockAndWake) { + create(orderUnlockAndWake); + register(); + } + + private void create(boolean orderUnlockAndWake) { mContext.getOrCreateTestableResources().addOverride( com.android.internal.R.bool.config_orderUnlockAndWake, orderUnlockAndWake); @@ -1189,7 +1216,9 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { mSelectedUserInteractor, mKeyguardInteractor); mViewMediator.start(); + } + private void register() { mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt index 20d5c5de3ffa..49953a1176fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.pipeline.mobile.domain.interactor +import android.platform.test.annotations.EnableFlags import android.telephony.CellSignalStrength import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN @@ -159,10 +160,13 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun numberOfLevels_comesFromRepo() = + fun numberOfLevels_comesFromRepo_whenApplicable() = testScope.runTest { var latest: Int? = null - val job = underTest.signalLevelIcon.onEach { latest = it.numberOfLevels }.launchIn(this) + val job = + underTest.signalLevelIcon + .onEach { latest = (it as? SignalIconModel.Cellular)?.numberOfLevels } + .launchIn(this) connectionRepository.numberOfLevels.value = 5 assertThat(latest).isEqualTo(5) @@ -491,14 +495,19 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun iconId_correctLevel_notCutout() = + fun cellBasedIconId_correctLevel_notCutout() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = true connectionRepository.primaryLevel.value = 1 connectionRepository.setDataEnabled(false) + connectionRepository.isNonTerrestrial.value = false - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) assertThat(latest?.level).isEqualTo(1) assertThat(latest?.showExclamationMark).isFalse() @@ -509,6 +518,7 @@ class MobileIconInteractorTest : SysuiTestCase() { @Test fun icon_usesLevelFromInteractor() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = true var latest: SignalIconModel? = null @@ -524,10 +534,15 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_usesNumberOfLevelsFromInteractor() = + fun cellBasedIcon_usesNumberOfLevelsFromInteractor() = testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + connectionRepository.isNonTerrestrial.value = false + + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) connectionRepository.numberOfLevels.value = 5 assertThat(latest!!.numberOfLevels).isEqualTo(5) @@ -539,12 +554,16 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_defaultDataDisabled_showExclamationTrue() = + fun cellBasedIcon_defaultDataDisabled_showExclamationTrue() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) assertThat(latest!!.showExclamationMark).isTrue() @@ -552,12 +571,16 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_defaultConnectionFailed_showExclamationTrue() = + fun cellBasedIcon_defaultConnectionFailed_showExclamationTrue() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false mobileIconsInteractor.isDefaultConnectionFailed.value = true - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) assertThat(latest!!.showExclamationMark).isTrue() @@ -565,14 +588,18 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_enabledAndNotFailed_showExclamationFalse() = + fun cellBasedIcon_enabledAndNotFailed_showExclamationFalse() = testScope.runTest { + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = true mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true mobileIconsInteractor.isDefaultConnectionFailed.value = false - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) assertThat(latest!!.showExclamationMark).isFalse() @@ -580,11 +607,15 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_usesEmptyState_whenNotInService() = + fun cellBasedIcon_usesEmptyState_whenNotInService() = testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular } + .launchIn(this) + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = false assertThat(latest?.level).isEqualTo(0) @@ -604,11 +635,15 @@ class MobileIconInteractorTest : SysuiTestCase() { } @Test - fun icon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = + fun cellBasedIcon_usesCarrierNetworkState_whenInCarrierNetworkChangeMode() = testScope.runTest { - var latest: SignalIconModel? = null - val job = underTest.signalLevelIcon.onEach { latest = it }.launchIn(this) + var latest: SignalIconModel.Cellular? = null + val job = + underTest.signalLevelIcon + .onEach { latest = it as? SignalIconModel.Cellular? } + .launchIn(this) + connectionRepository.isNonTerrestrial.value = false connectionRepository.isInService.value = true connectionRepository.carrierNetworkChangeActive.value = true connectionRepository.primaryLevel.value = 1 @@ -626,6 +661,20 @@ class MobileIconInteractorTest : SysuiTestCase() { job.cancel() } + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun satBasedIcon_isUsedWhenNonTerrestrial() = + testScope.runTest { + val latest by collectLastValue(underTest.signalLevelIcon) + + // Start off using cellular + assertThat(latest).isInstanceOf(SignalIconModel.Cellular::class.java) + + connectionRepository.isNonTerrestrial.value = true + + assertThat(latest).isInstanceOf(SignalIconModel.Satellite::class.java) + } + private fun createInteractor( overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl() ) = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt index 90a894648c9f..ebec00380cf4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/model/SignalIconModelParameterizedTest.kt @@ -32,7 +32,7 @@ internal class SignalIconModelParameterizedTest(private val testCase: TestCase) @Test fun drawableFromModel_level0_numLevels4_noExclamation_notCarrierNetworkChange() { val model = - SignalIconModel( + SignalIconModel.Cellular( level = 0, numberOfLevels = 4, showExclamationMark = false, @@ -59,7 +59,7 @@ internal class SignalIconModelParameterizedTest(private val testCase: TestCase) val expected: Int, ) { fun toSignalIconModel() = - SignalIconModel( + SignalIconModel.Cellular( level = level, numberOfLevels = numberOfLevels, showExclamationMark = showExclamation, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt index 889130f47820..deb9fcf4b56e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt @@ -190,7 +190,7 @@ class LocationBasedMobileIconViewModelTest : SysuiTestCase() { /** Convenience constructor for these tests */ fun defaultSignal(level: Int = 1): SignalIconModel { - return SignalIconModel( + return SignalIconModel.Cellular( level, NUM_LEVELS, showExclamationMark = false, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt index 147efcbd67c4..83d0fe8f9c4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt @@ -55,6 +55,7 @@ import com.android.systemui.util.CarrierConfigTracker import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope @@ -709,6 +710,87 @@ class MobileIconViewModelTest : SysuiTestCase() { .isEqualTo(Icon.Resource(R.drawable.mobile_network_type_background, null)) } + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun nonTerrestrial_defaultProperties() = + testScope.runTest { + repository.isNonTerrestrial.value = true + + val roaming by collectLastValue(underTest.roaming) + val networkTypeIcon by collectLastValue(underTest.networkTypeIcon) + val networkTypeBackground by collectLastValue(underTest.networkTypeBackground) + val activityInVisible by collectLastValue(underTest.activityInVisible) + val activityOutVisible by collectLastValue(underTest.activityOutVisible) + val activityContainerVisible by collectLastValue(underTest.activityContainerVisible) + + assertThat(roaming).isFalse() + assertThat(networkTypeIcon).isNull() + assertThat(networkTypeBackground).isNull() + assertThat(activityInVisible).isFalse() + assertThat(activityOutVisible).isFalse() + assertThat(activityContainerVisible).isFalse() + } + + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun nonTerrestrial_ignoresDefaultProperties() = + testScope.runTest { + repository.isNonTerrestrial.value = true + + val roaming by collectLastValue(underTest.roaming) + val networkTypeIcon by collectLastValue(underTest.networkTypeIcon) + val networkTypeBackground by collectLastValue(underTest.networkTypeBackground) + val activityInVisible by collectLastValue(underTest.activityInVisible) + val activityOutVisible by collectLastValue(underTest.activityOutVisible) + val activityContainerVisible by collectLastValue(underTest.activityContainerVisible) + + repository.setAllRoaming(true) + repository.setNetworkTypeKey(connectionsRepository.LTE_KEY) + // sets the background on cellular + repository.hasPrioritizedNetworkCapabilities.value = true + repository.dataActivityDirection.value = + DataActivityModel( + hasActivityIn = true, + hasActivityOut = true, + ) + + assertThat(roaming).isFalse() + assertThat(networkTypeIcon).isNull() + assertThat(networkTypeBackground).isNull() + assertThat(activityInVisible).isFalse() + assertThat(activityOutVisible).isFalse() + assertThat(activityContainerVisible).isFalse() + } + + @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + @Test + fun nonTerrestrial_usesSatelliteIcon() = + testScope.runTest { + repository.isNonTerrestrial.value = true + repository.setAllLevels(0) + + val latest by + collectLastValue(underTest.icon.filterIsInstance(SignalIconModel.Satellite::class)) + + // Level 0 -> no connection + assertThat(latest).isNotNull() + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_0) + + // 1-2 -> 1 bar + repository.setAllLevels(1) + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + repository.setAllLevels(2) + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_1) + + // 3-4 -> 2 bars + repository.setAllLevels(3) + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + + repository.setAllLevels(4) + assertThat(latest!!.icon.res).isEqualTo(R.drawable.ic_satellite_connected_2) + } + private fun createAndSetViewModel() { underTest = MobileIconViewModel( @@ -723,24 +805,5 @@ class MobileIconViewModelTest : SysuiTestCase() { companion object { private const val SUB_1_ID = 1 - private const val NUM_LEVELS = 4 - - /** Convenience constructor for these tests */ - fun defaultSignal(level: Int = 1): SignalIconModel { - return SignalIconModel( - level, - NUM_LEVELS, - showExclamationMark = false, - carrierNetworkChange = false, - ) - } - - fun emptySignal(): SignalIconModel = - SignalIconModel( - level = 0, - numberOfLevels = NUM_LEVELS, - showExclamationMark = true, - carrierNetworkChange = false, - ) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt index 5f4d7bf6f371..c01032757bb0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt @@ -60,6 +60,8 @@ class FakeMobileIconInteractor( override val isInService = MutableStateFlow(true) + override val isNonTerrestrial = MutableStateFlow(false) + private val _isDataEnabled = MutableStateFlow(true) override val isDataEnabled = _isDataEnabled @@ -69,7 +71,7 @@ class FakeMobileIconInteractor( override val signalLevelIcon: MutableStateFlow<SignalIconModel> = MutableStateFlow( - SignalIconModel( + SignalIconModel.Cellular( level = 0, numberOfLevels = 4, showExclamationMark = false, diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 9b1fade198fc..afb8345249b1 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -4491,6 +4491,12 @@ public final class ActiveServices { } } if (userId > 0) { + if (mAm.isSystemUserOnly(sInfo.flags)) { + Slog.w(TAG_SERVICE, service + " is only available for the SYSTEM user," + + " calling userId is: " + userId); + return null; + } + if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo, sInfo.name, sInfo.flags) && mAm.isValidSingletonCall(callingUid, sInfo.applicationInfo.uid)) { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 86894fd9b405..3de224addf95 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13763,6 +13763,11 @@ public class ActivityManagerService extends IActivityManager.Stub return result; } + boolean isSystemUserOnly(int flags) { + return android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders() + && (flags & ServiceInfo.FLAG_SYSTEM_USER_ONLY) != 0; + } + /** * Checks to see if the caller is in the same app as the singleton * component, or the component is in a special app. It allows special apps diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index 095d907d7df6..30f21a65b5b1 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -1249,9 +1249,9 @@ public class ContentProviderHelper { ProviderInfo cpi = providers.get(i); boolean singleton = mService.isSingleton(cpi.processName, cpi.applicationInfo, cpi.name, cpi.flags); - if (singleton && app.userId != UserHandle.USER_SYSTEM) { - // This is a singleton provider, but a user besides the - // default user is asking to initialize a process it runs + if (isSingletonOrSystemUserOnly(cpi) && app.userId != UserHandle.USER_SYSTEM) { + // This is a singleton or a SYSTEM user only provider, but a user besides the + // SYSTEM user is asking to initialize a process it runs // in... well, no, it doesn't actually run in this process, // it runs in the process of the default user. Get rid of it. providers.remove(i); @@ -1398,8 +1398,7 @@ public class ContentProviderHelper { final boolean processMatch = Objects.equals(pi.processName, app.processName) || pi.multiprocess; - final boolean userMatch = !mService.isSingleton( - pi.processName, pi.applicationInfo, pi.name, pi.flags) + final boolean userMatch = !isSingletonOrSystemUserOnly(pi) || app.userId == UserHandle.USER_SYSTEM; final boolean isInstantApp = pi.applicationInfo.isInstantApp(); final boolean splitInstalled = pi.splitName == null @@ -1985,4 +1984,13 @@ public class ContentProviderHelper { return isAuthRedirected; } } + + /** + * Returns true if Provider is either singleUser or systemUserOnly provider. + */ + private boolean isSingletonOrSystemUserOnly(ProviderInfo pi) { + return (android.multiuser.Flags.enableSystemUserOnlyForServicesAndProviders() + && mService.isSystemUserOnly(pi.flags)) + || mService.isSingleton(pi.processName, pi.applicationInfo, pi.name, pi.flags); + } } diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 9db5d0a99480..dc14c7aaa0b9 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -162,6 +162,7 @@ public class SettingsToPropertiesMapper { "pdf_viewer", "pixel_audio_android", "pixel_bluetooth", + "pixel_connectivity_gps", "pixel_system_sw_video", "pixel_watch", "platform_security", diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index beb68d3566c3..50340d241347 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1167,7 +1167,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // sender userId can be a real user ID or USER_ALL. final int senderUserId = pendingResult.getSendingUserId(); if (senderUserId != UserHandle.USER_ALL) { - if (senderUserId != mSettings.getCurrentUserId()) { + if (senderUserId != mSettings.getUserId()) { // A background user is trying to hide the dialog. Ignore. return; } @@ -1245,7 +1245,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean isChangingPackagesOfCurrentUserLocked() { final int userId = getChangingUserId(); - final boolean retval = userId == mSettings.getCurrentUserId(); + final boolean retval = userId == mSettings.getUserId(); if (DEBUG) { if (!retval) { Slog.d(TAG, "--- ignore this call back from a background user: " + userId); @@ -1344,8 +1344,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (changed) { AdditionalSubtypeUtils.save( - mAdditionalSubtypeMap, mSettings.getMethodMap(), - mSettings.getCurrentUserId()); + mAdditionalSubtypeMap, mSettings.getMethodMap(), mSettings.getUserId()); mChangedPackages.add(packageName); } } @@ -1425,8 +1424,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + imi.getComponent()); mAdditionalSubtypeMap.remove(imi.getId()); AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, - mSettings.getMethodMap(), - mSettings.getCurrentUserId()); + mSettings.getMethodMap(), mSettings.getUserId()); } } } @@ -1440,7 +1438,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (change == PACKAGE_TEMPORARY_CHANGE || change == PACKAGE_PERMANENT_CHANGE) { final PackageManager userAwarePackageManager = - getPackageManagerForUser(mContext, mSettings.getCurrentUserId()); + getPackageManagerForUser(mContext, mSettings.getUserId()); ServiceInfo si = null; try { si = userAwarePackageManager.getServiceInfo(curIm.getComponent(), @@ -1576,7 +1574,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub void onUnlockUser(@UserIdInt int userId) { synchronized (ImfLock.class) { - final int currentUserId = mSettings.getCurrentUserId(); + final int currentUserId = mSettings.getUserId(); if (DEBUG) { Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId); } @@ -1665,7 +1663,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mSettings.getMethodMap(), userId); mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(mSettings.getMethodMap(), - mSettings.getCurrentUserId()); + mSettings.getUserId()); mMenuController = new InputMethodMenuController(this); mBindingController = bindingControllerForTesting != null @@ -1696,7 +1694,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") @UserIdInt int getCurrentImeUserIdLocked() { - return mSettings.getCurrentUserId(); + return mSettings.getUserId(); } private final class InkWindowInitializer implements Runnable { @@ -1784,7 +1782,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub IInputMethodClientInvoker clientToBeReset) { if (DEBUG) { Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId - + " currentUserId=" + mSettings.getCurrentUserId()); + + " currentUserId=" + mSettings.getUserId()); } maybeInitImeNavbarConfigLocked(newUserId); @@ -1854,7 +1852,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (!mSystemReady) { mSystemReady = true; - final int currentUserId = mSettings.getCurrentUserId(); + final int currentUserId = mSettings.getUserId(); mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class); hideStatusBarIconLocked(); @@ -1875,7 +1873,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // the "mImeDrawsImeNavBarResLazyInitFuture" field. synchronized (ImfLock.class) { mImeDrawsImeNavBarResLazyInitFuture = null; - if (currentUserId != mSettings.getCurrentUserId()) { + if (currentUserId != mSettings.getUserId()) { // This means that the current user is already switched to other user // before the background task is executed. In this scenario the relevant // field should already be initialized. @@ -1947,7 +1945,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mSettings.getCurrentUserId(), null); + mSettings.getUserId(), null); if (resolvedUserIds.length != 1) { return Collections.emptyList(); } @@ -1970,7 +1968,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, - mSettings.getCurrentUserId(), null); + mSettings.getUserId(), null); if (resolvedUserIds.length != 1) { return Collections.emptyList(); } @@ -1997,7 +1995,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // Check if selected IME of current user supports handwriting. - if (userId == mSettings.getCurrentUserId()) { + if (userId == mSettings.getUserId()) { return mBindingController.supportsStylusHandwriting(); } //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList. @@ -2025,7 +2023,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId, @DirectBootAwareness int directBootAwareness, int callingUid) { final InputMethodSettings settings; - if (userId == mSettings.getCurrentUserId() + if (userId == mSettings.getUserId() && directBootAwareness == DirectBootAwareness.AUTO) { settings = mSettings; } else { @@ -2048,7 +2046,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub int callingUid) { final ArrayList<InputMethodInfo> methodList; final InputMethodSettings settings; - if (userId == mSettings.getCurrentUserId()) { + if (userId == mSettings.getUserId()) { methodList = mSettings.getEnabledInputMethodList(); settings = mSettings; } else { @@ -2112,7 +2110,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) { - if (userId == mSettings.getCurrentUserId()) { + if (userId == mSettings.getUserId()) { final InputMethodInfo imi; String selectedMethodId = getSelectedMethodIdLocked(); if (imiId == null && selectedMethodId != null) { @@ -2300,7 +2298,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final boolean restarting = !initial; final Binder startInputToken = new Binder(); - final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(), + final StartInputInfo info = new StartInputInfo(mSettings.getUserId(), getCurTokenLocked(), mCurTokenDisplayId, getCurIdLocked(), startInputReason, restarting, UserHandle.getUserId(mCurClient.mUid), @@ -2315,9 +2313,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // same-user scenarios. // That said ignoring cross-user scenario will never affect IMEs that do not have // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. - if (mSettings.getCurrentUserId() == UserHandle.getUserId( + if (mSettings.getUserId() == UserHandle.getUserId( mCurClient.mUid)) { - mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(), + mPackageManagerInternal.grantImplicitAccess(mSettings.getUserId(), null /* intent */, UserHandle.getAppId(getCurMethodUidLocked()), mCurClient.mUid, true /* direct */); } @@ -2939,7 +2937,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } else if (packageName != null) { if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); final PackageManager userAwarePackageManager = - getPackageManagerForUser(mContext, mSettings.getCurrentUserId()); + getPackageManagerForUser(mContext, mSettings.getUserId()); ApplicationInfo applicationInfo = null; try { applicationInfo = userAwarePackageManager.getApplicationInfo(packageName, @@ -3001,7 +2999,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return false; } if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() - && mWindowManagerInternal.isKeyguardSecure(mSettings.getCurrentUserId())) { + && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId())) { return false; } if ((visibility & InputMethodService.IME_ACTIVE) == 0 @@ -3180,7 +3178,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) { if (enabledMayChange) { final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext, - mSettings.getCurrentUserId()); + mSettings.getUserId()); List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodList(); for (int i = 0; i < enabled.size(); i++) { @@ -3228,18 +3226,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // TODO: Instantiate mSwitchingController for each user. - if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) { + if (mSettings.getUserId() == mSwitchingController.getUserId()) { mSwitchingController.resetCircularListLocked(mSettings.getMethodMap()); } else { mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, mSettings.getMethodMap(), mSettings.getCurrentUserId()); + mContext, mSettings.getMethodMap(), mSettings.getUserId()); } // TODO: Instantiate mHardwareKeyboardShortcutController for each user. - if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) { + if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap()); } else { mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - mSettings.getMethodMap(), mSettings.getCurrentUserId()); + mSettings.getMethodMap(), mSettings.getUserId()); } sendOnNavButtonFlagsChangedLocked(); } @@ -3270,7 +3268,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // See if we need to notify a subtype change within the same IME. if (id.equals(getSelectedMethodIdLocked())) { - final int userId = mSettings.getCurrentUserId(); + final int userId = mSettings.getUserId(); final int subtypeCount = info.getSubtypeCount(); if (subtypeCount <= 0) { notifyInputMethodSubtypeChangedLocked(userId, info, null); @@ -3781,7 +3779,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return InputBindResult.USER_SWITCHING; } final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds( - mSettings.getCurrentUserId(), false /* enabledOnly */); + mSettings.getUserId(), false /* enabledOnly */); for (int profileId : profileIdsWithDisabled) { if (profileId == userId) { scheduleSwitchUserTaskLocked(userId, cs.mClient); @@ -3800,7 +3798,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mVisibilityStateComputer.mShowForced = false; } - final int currentUserId = mSettings.getCurrentUserId(); + final int currentUserId = mSettings.getUserId(); if (userId != currentUserId) { if (ArrayUtils.contains( mUserManagerInternal.getProfileIds(currentUserId, false), userId)) { @@ -3944,7 +3942,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub && mCurFocusedWindowClient.mClient.asBinder() == client.asBinder()) { return true; } - if (mSettings.getCurrentUserId() != UserHandle.getUserId(uid)) { + if (mSettings.getUserId() != UserHandle.getUserId(uid)) { return false; } if (getCurIntentLocked() != null && InputMethodUtils.checkIfPackageBelongsToUid( @@ -4085,8 +4083,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub && !TextUtils.isEmpty(mCurrentSubtype.getLocale())) { locale = mCurrentSubtype.getLocale(); } else { - locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId()).get(0) - .toString(); + locale = SystemLocaleWrapper.get(mSettings.getUserId()).get(0).toString(); } for (int i = 0; i < enabledCount; ++i) { final InputMethodInfo imi = enabled.get(i); @@ -4164,7 +4161,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { - if (mSettings.getCurrentUserId() == userId) { + if (mSettings.getUserId() == userId) { return mSettings.getLastInputMethodSubtype(); } @@ -4199,7 +4196,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return; } - if (mSettings.getCurrentUserId() == userId) { + if (mSettings.getUserId() == userId) { if (!mSettings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, mAdditionalSubtypeMap, mPackageManagerInternal, callingUid)) { return; @@ -4243,7 +4240,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final long ident = Binder.clearCallingIdentity(); try { synchronized (ImfLock.class) { - final boolean currentUser = (mSettings.getCurrentUserId() == userId); + final boolean currentUser = (mSettings.getUserId() == userId); final InputMethodSettings settings = currentUser ? mSettings : queryMethodMapForUser(userId); if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) { @@ -4614,7 +4611,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } return; } - if (mSettings.getCurrentUserId() != mSwitchingController.getUserId()) { + if (mSettings.getUserId() != mSwitchingController.getUserId()) { return; } final InputMethodInfo imi = @@ -4849,8 +4846,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked() - && mWindowManagerInternal.isKeyguardSecure( - mSettings.getCurrentUserId()); + && mWindowManagerInternal.isKeyguardSecure(mSettings.getUserId()); final String lastInputMethodId = mSettings.getSelectedInputMethod(); int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId); @@ -4858,8 +4854,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController .getSortedInputMethodAndSubtypeList( showAuxSubtypes, isScreenLocked, true /* forImeMenu */, - mContext, mSettings.getMethodMap(), - mSettings.getCurrentUserId()); + mContext, mSettings.getMethodMap(), mSettings.getUserId()); mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId, lastInputMethodId, lastInputMethodSubtypeId, imList); } @@ -5157,7 +5152,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mMethodMapUpdateCount++; mMyPackageMonitor.clearKnownImePackageNamesLocked(); - mSettings = queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(), + mSettings = queryInputMethodServicesInternal(mContext, mSettings.getUserId(), mAdditionalSubtypeMap, DirectBootAwareness.AUTO); // Construct the set of possible IME packages for onPackageChanged() to avoid false @@ -5170,7 +5165,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final List<ResolveInfo> allInputMethodServices = mContext.getPackageManager().queryIntentServicesAsUser( new Intent(InputMethod.SERVICE_INTERFACE), - PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getCurrentUserId()); + PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getUserId()); final int numImes = allInputMethodServices.size(); for (int i = 0; i < numImes; ++i) { final ServiceInfo si = allInputMethodServices.get(i).serviceInfo; @@ -5241,18 +5236,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub updateDefaultVoiceImeIfNeededLocked(); // TODO: Instantiate mSwitchingController for each user. - if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) { + if (mSettings.getUserId() == mSwitchingController.getUserId()) { mSwitchingController.resetCircularListLocked(mSettings.getMethodMap()); } else { mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( - mContext, mSettings.getMethodMap(), mSettings.getCurrentUserId()); + mContext, mSettings.getMethodMap(), mSettings.getUserId()); } // TODO: Instantiate mHardwareKeyboardShortcutController for each user. - if (mSettings.getCurrentUserId() == mHardwareKeyboardShortcutController.getUserId()) { + if (mSettings.getUserId() == mHardwareKeyboardShortcutController.getUserId()) { mHardwareKeyboardShortcutController.reset(mSettings.getMethodMap()); } else { mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController( - mSettings.getMethodMap(), mSettings.getCurrentUserId()); + mSettings.getMethodMap(), mSettings.getUserId()); } sendOnNavButtonFlagsChangedLocked(); @@ -5260,7 +5255,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Notify InputMethodListListeners of the new installed InputMethods. final List<InputMethodInfo> inputMethodList = mSettings.getMethodList(); mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED, - mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget(); + mSettings.getUserId(), 0 /* unused */, inputMethodList).sendToTarget(); } @GuardedBy("ImfLock.class") @@ -5381,7 +5376,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mCurrentSubtype = getCurrentInputMethodSubtypeLocked(); } } - notifyInputMethodSubtypeChangedLocked(mSettings.getCurrentUserId(), imi, mCurrentSubtype); + notifyInputMethodSubtypeChangedLocked(mSettings.getUserId(), imi, mCurrentSubtype); if (!setSubtypeOnly) { // Set InputMethod here @@ -5422,7 +5417,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } synchronized (ImfLock.class) { - if (mSettings.getCurrentUserId() == userId) { + if (mSettings.getUserId() == userId) { return getCurrentInputMethodSubtypeLocked(); } @@ -5466,7 +5461,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { - final String locale = SystemLocaleWrapper.get(mSettings.getCurrentUserId()) + final String locale = SystemLocaleWrapper.get(mSettings.getUserId()) .get(0).toString(); mCurrentSubtype = SubtypeUtils.findLastResortApplicableSubtype( explicitlyOrImplicitlyEnabledSubtypes, @@ -5490,7 +5485,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private InputMethodInfo queryDefaultInputMethodForUserIdLocked(@UserIdInt int userId) { final InputMethodSettings settings; - if (userId == mSettings.getCurrentUserId()) { + if (userId == mSettings.getUserId()) { settings = mSettings; } else { final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = @@ -5512,7 +5507,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") private boolean switchToInputMethodLocked(String imeId, @UserIdInt int userId) { - if (userId == mSettings.getCurrentUserId()) { + if (userId == mSettings.getUserId()) { if (!mSettings.getMethodMap().containsKey(imeId) || !mSettings.getEnabledInputMethodList() .contains(mSettings.getMethodMap().get(imeId))) { @@ -5654,7 +5649,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) { synchronized (ImfLock.class) { - if (userId == mSettings.getCurrentUserId()) { + if (userId == mSettings.getUserId()) { if (!mSettings.getMethodMap().containsKey(imeId)) { return false; // IME is not found. } @@ -6296,7 +6291,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); + mSettings.getUserId(), shellCommand.getErrPrintWriter()); try (PrintWriter pr = shellCommand.getOutPrintWriter()) { for (int userId : userIds) { final List<InputMethodInfo> methods = all @@ -6341,7 +6336,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub PrintWriter error = shellCommand.getErrPrintWriter()) { synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); + mSettings.getUserId(), shellCommand.getErrPrintWriter()); for (int userId : userIds) { if (!userHasDebugPriv(userId, shellCommand)) { continue; @@ -6400,7 +6395,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub PrintWriter error) { boolean failedToEnableUnknownIme = false; boolean previouslyEnabled = false; - if (userId == mSettings.getCurrentUserId()) { + if (userId == mSettings.getUserId()) { if (enabled && !mSettings.getMethodMap().containsKey(imeId)) { failedToEnableUnknownIme = true; } else { @@ -6462,7 +6457,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub PrintWriter error = shellCommand.getErrPrintWriter()) { synchronized (ImfLock.class) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); + mSettings.getUserId(), shellCommand.getErrPrintWriter()); for (int userId : userIds) { if (!userHasDebugPriv(userId, shellCommand)) { continue; @@ -6502,7 +6497,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { try (PrintWriter out = shellCommand.getOutPrintWriter()) { final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, - mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); + mSettings.getUserId(), shellCommand.getErrPrintWriter()); for (int userId : userIds) { if (!userHasDebugPriv(userId, shellCommand)) { continue; @@ -6514,7 +6509,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } final String nextIme; final List<InputMethodInfo> nextEnabledImes; - if (userId == mSettings.getCurrentUserId()) { + if (userId == mSettings.getUserId()) { hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, null /* resultReceiver */, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); @@ -6537,7 +6532,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( - getPackageManagerForUser(mContext, mSettings.getCurrentUserId()), + getPackageManagerForUser(mContext, mSettings.getUserId()), mSettings.getEnabledInputMethodList()); nextIme = mSettings.getSelectedInputMethod(); nextEnabledImes = mSettings.getEnabledInputMethodList(); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java index abd7688b60cc..a51002be344f 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java @@ -63,7 +63,7 @@ final class InputMethodSettings { private final List<InputMethodInfo> mMethodList; @UserIdInt - private final int mCurrentUserId; + private final int mUserId; private static void buildEnabledInputMethodsSettingString( StringBuilder builder, Pair<String, ArrayList<String>> ime) { @@ -87,7 +87,7 @@ final class InputMethodSettings { private InputMethodSettings(InputMethodMap methodMap, @UserIdInt int userId) { mMethodMap = methodMap; mMethodList = methodMap.values(); - mCurrentUserId = userId; + mUserId = userId; String ime = getSelectedInputMethod(); String defaultDeviceIme = getSelectedDefaultDeviceInputMethod(); if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) { @@ -109,20 +109,20 @@ final class InputMethodSettings { } private void putString(@NonNull String key, @Nullable String str) { - SecureSettingsWrapper.putString(key, str, mCurrentUserId); + SecureSettingsWrapper.putString(key, str, mUserId); } @Nullable private String getString(@NonNull String key, @Nullable String defaultValue) { - return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId); + return SecureSettingsWrapper.getString(key, defaultValue, mUserId); } private void putInt(String key, int value) { - SecureSettingsWrapper.putInt(key, value, mCurrentUserId); + SecureSettingsWrapper.putInt(key, value, mUserId); } private int getInt(String key, int defaultValue) { - return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId); + return SecureSettingsWrapper.getInt(key, defaultValue, mUserId); } ArrayList<InputMethodInfo> getEnabledInputMethodList() { @@ -142,7 +142,7 @@ final class InputMethodSettings { getEnabledInputMethodSubtypeList(imi); if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) { enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypes( - SystemLocaleWrapper.get(mCurrentUserId), imi); + SystemLocaleWrapper.get(mUserId), imi); } return InputMethodSubtype.sort(imi, enabledSubtypes); } @@ -394,7 +394,7 @@ final class InputMethodSettings { private String getEnabledSubtypeHashCodeForInputMethodAndSubtype(List<Pair<String, ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { - final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId); + final LocaleList localeList = SystemLocaleWrapper.get(mUserId); for (int i = 0; i < enabledImes.size(); ++i) { final Pair<String, ArrayList<String>> enabledIme = enabledImes.get(i); if (enabledIme.first.equals(imeId)) { @@ -483,16 +483,14 @@ final class InputMethodSettings { void putSelectedInputMethod(String imeId) { if (DEBUG) { - Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " - + mCurrentUserId); + Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " + mUserId); } putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId); } void putSelectedSubtype(int subtypeId) { if (DEBUG) { - Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " - + mCurrentUserId); + Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " + mUserId); } putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); } @@ -510,24 +508,21 @@ final class InputMethodSettings { String getSelectedDefaultDeviceInputMethod() { final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null); if (DEBUG) { - Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", " - + mCurrentUserId); + Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", " + mUserId); } return imi; } void putSelectedDefaultDeviceInputMethod(String imeId) { if (DEBUG) { - Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", " - + mCurrentUserId); + Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", " + mUserId); } putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId); } void putDefaultVoiceInputMethod(String imeId) { if (DEBUG) { - Slog.d(TAG, - "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId); + Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mUserId); } putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId); } @@ -551,8 +546,8 @@ final class InputMethodSettings { } @UserIdInt - public int getCurrentUserId() { - return mCurrentUserId; + public int getUserId() { + return mUserId; } int getSelectedInputMethodSubtypeId(String selectedImiId) { @@ -615,7 +610,7 @@ final class InputMethodSettings { if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { return explicitlyOrImplicitlyEnabledSubtypes.get(0); } - final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString(); + final String locale = SystemLocaleWrapper.get(mUserId).get(0).toString(); final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtype( explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true); @@ -644,7 +639,7 @@ final class InputMethodSettings { } else { additionalSubtypeMap.put(imi.getId(), subtypes); } - AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId()); + AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getUserId()); return true; } @@ -715,6 +710,6 @@ final class InputMethodSettings { } void dump(final Printer pw, final String prefix) { - pw.println(prefix + "mCurrentUserId=" + mCurrentUserId); + pw.println(prefix + "mUserId=" + mUserId); } } diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 45dd02c9b7be..f3f183815d0b 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -56,6 +56,7 @@ #include "java/JavaClassGenerator.h" #include "java/ManifestClassGenerator.h" #include "java/ProguardRules.h" +#include "link/FeatureFlagsFilter.h" #include "link/Linkers.h" #include "link/ManifestFixer.h" #include "link/NoDefaultResourceRemover.h" @@ -1987,6 +1988,19 @@ class Linker { context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()}); context_->SetSplitNameDependencies(app_info_.split_name_dependencies); + FeatureFlagsFilterOptions flags_filter_options; + if (context_->GetMinSdkVersion() > SDK_UPSIDE_DOWN_CAKE) { + // For API version > U, PackageManager will dynamically read the flag values and disable + // manifest elements accordingly when parsing the manifest. + // For API version <= U, we remove disabled elements from the manifest with the filter. + flags_filter_options.remove_disabled_elements = false; + flags_filter_options.flags_must_have_value = false; + } + FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options); + if (!flags_filter.Consume(context_, manifest_xml.get())) { + return 1; + } + // Override the package ID when it is "android". if (context_->GetCompilationPackage() == "android") { context_->SetPackageId(kAndroidPackageId); @@ -2531,7 +2545,7 @@ int LinkCommand::Action(const std::vector<std::string>& args) { } for (const std::string& arg : all_feature_flags_args) { - if (ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) { + if (!ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) { return 1; } } diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index 26713fd92264..dc18b1ccda60 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -330,7 +330,11 @@ class LinkCommand : public Command { "should only be used together with the --static-lib flag.", &options_.merge_only); AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_); - AddOptionalFlagList("--feature-flags", "Placeholder, to be implemented.", &feature_flags_args_); + AddOptionalFlagList("--feature-flags", + "Specify the values of feature flags. The pairs in the argument\n" + "are separated by ',' and the name is separated from the value by '='.\n" + "Example: \"flag1=true,flag2=false,flag3=\" (flag3 has no given value).", + &feature_flags_args_); } int Action(const std::vector<std::string>& args) override; diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 7096f5cc54e3..9323f3b95eac 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -16,11 +16,10 @@ #include "Link.h" -#include <android-base/file.h> - -#include "AppInfo.h" #include "Diagnostics.h" #include "LoadedApk.h" +#include "android-base/file.h" +#include "android-base/stringprintf.h" #include "test/Test.h" using testing::Eq; @@ -993,4 +992,213 @@ TEST_F(LinkTest, LocaleConfigWrongLocaleFormat) { ASSERT_FALSE(Link(link_args, &diag)); } +static void BuildSDKWithFeatureFlagAttr(const std::string& apk_path, const std::string& java_path, + CommandTestFixture* fixture, android::IDiagnostics* diag) { + const std::string android_values = + R"(<resources> + <staging-public-group type="attr" first-id="0x01fe0063"> + <public name="featureFlag" /> + </staging-public-group> + <attr name="featureFlag" format="string" /> + </resources>)"; + + SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values}; + BuildSDK({source_xml}, apk_path, java_path, fixture, diag); +} + +TEST_F(LinkTest, FeatureFlagDisabled_SdkAtMostUDC) { + StdErrDiagnostics diag; + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android-java"); + BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag); + + const std::string manifest_contents = android::base::StringPrintf( + R"(<uses-sdk android:minSdkVersion="%d" />" + <permission android:name="FOO" android:featureFlag="flag" />)", + SDK_UPSIDE_DOWN_CAKE); + auto app_manifest = ManifestBuilder(this) + .SetPackageName("com.example.app") + .AddContents(manifest_contents) + .Build(); + + auto app_link_args = LinkCommandBuilder(this) + .SetManifestFile(app_manifest) + .AddParameter("-I", android_apk) + .AddParameter("--feature-flags", "flag=false"); + + const std::string app_apk = GetTestPath("app.apk"); + BuildApk({}, app_apk, std::move(app_link_args), this, &diag); + + // Permission element should be removed if flag is disabled + auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag); + ASSERT_THAT(apk, NotNull()); + auto apk_manifest = apk->GetManifest(); + ASSERT_THAT(apk_manifest, NotNull()); + auto root = apk_manifest->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, IsNull()); +} + +TEST_F(LinkTest, FeatureFlagEnabled_SdkAtMostUDC) { + StdErrDiagnostics diag; + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android-java"); + BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag); + + const std::string manifest_contents = android::base::StringPrintf( + R"(<uses-sdk android:minSdkVersion="%d" />" + <permission android:name="FOO" android:featureFlag="flag" />)", + SDK_UPSIDE_DOWN_CAKE); + auto app_manifest = ManifestBuilder(this) + .SetPackageName("com.example.app") + .AddContents(manifest_contents) + .Build(); + + auto app_link_args = LinkCommandBuilder(this) + .SetManifestFile(app_manifest) + .AddParameter("-I", android_apk) + .AddParameter("--feature-flags", "flag=true"); + + const std::string app_apk = GetTestPath("app.apk"); + BuildApk({}, app_apk, std::move(app_link_args), this, &diag); + + // Permission element should be kept if flag is enabled + auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag); + ASSERT_THAT(apk, NotNull()); + auto apk_manifest = apk->GetManifest(); + ASSERT_THAT(apk_manifest, NotNull()); + auto root = apk_manifest->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, NotNull()); +} + +TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAtMostUDC) { + StdErrDiagnostics diag; + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android-java"); + BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag); + + const std::string manifest_contents = android::base::StringPrintf( + R"(<uses-sdk android:minSdkVersion="%d" />" + <permission android:name="FOO" android:featureFlag="flag" />)", + SDK_UPSIDE_DOWN_CAKE); + auto app_manifest = ManifestBuilder(this) + .SetPackageName("com.example.app") + .AddContents(manifest_contents) + .Build(); + + auto app_link_args = LinkCommandBuilder(this) + .SetManifestFile(app_manifest) + .AddParameter("-I", android_apk) + .AddParameter("--feature-flags", "flag="); + + // Flags must have values if <= UDC + const std::string app_apk = GetTestPath("app.apk"); + ASSERT_FALSE(Link(app_link_args.Build(app_apk), &diag)); +} + +TEST_F(LinkTest, FeatureFlagDisabled_SdkAfterUDC) { + StdErrDiagnostics diag; + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android-java"); + BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag); + + const std::string manifest_contents = android::base::StringPrintf( + R"(<uses-sdk android:minSdkVersion="%d" />" + <permission android:name="FOO" android:featureFlag="flag" />)", + SDK_CUR_DEVELOPMENT); + auto app_manifest = ManifestBuilder(this) + .SetPackageName("com.example.app") + .AddContents(manifest_contents) + .Build(); + + auto app_link_args = LinkCommandBuilder(this) + .SetManifestFile(app_manifest) + .AddParameter("-I", android_apk) + .AddParameter("--feature-flags", "flag=false"); + + const std::string app_apk = GetTestPath("app.apk"); + BuildApk({}, app_apk, std::move(app_link_args), this, &diag); + + // Permission element should be kept if > UDC, regardless of flag value + auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag); + ASSERT_THAT(apk, NotNull()); + auto apk_manifest = apk->GetManifest(); + ASSERT_THAT(apk_manifest, NotNull()); + auto root = apk_manifest->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, NotNull()); +} + +TEST_F(LinkTest, FeatureFlagEnabled_SdkAfterUDC) { + StdErrDiagnostics diag; + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android-java"); + BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag); + + const std::string manifest_contents = android::base::StringPrintf( + R"(<uses-sdk android:minSdkVersion="%d" />" + <permission android:name="FOO" android:featureFlag="flag" />)", + SDK_CUR_DEVELOPMENT); + auto app_manifest = ManifestBuilder(this) + .SetPackageName("com.example.app") + .AddContents(manifest_contents) + .Build(); + + auto app_link_args = LinkCommandBuilder(this) + .SetManifestFile(app_manifest) + .AddParameter("-I", android_apk) + .AddParameter("--feature-flags", "flag=true"); + + const std::string app_apk = GetTestPath("app.apk"); + BuildApk({}, app_apk, std::move(app_link_args), this, &diag); + + // Permission element should be kept if > UDC, regardless of flag value + auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag); + ASSERT_THAT(apk, NotNull()); + auto apk_manifest = apk->GetManifest(); + ASSERT_THAT(apk_manifest, NotNull()); + auto root = apk_manifest->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, NotNull()); +} + +TEST_F(LinkTest, FeatureFlagWithNoValue_SdkAfterUDC) { + StdErrDiagnostics diag; + const std::string android_apk = GetTestPath("android.apk"); + const std::string android_java = GetTestPath("android-java"); + BuildSDKWithFeatureFlagAttr(android_apk, android_java, this, &diag); + + const std::string manifest_contents = android::base::StringPrintf( + R"(<uses-sdk android:minSdkVersion="%d" />" + <permission android:name="FOO" android:featureFlag="flag" />)", + SDK_CUR_DEVELOPMENT); + auto app_manifest = ManifestBuilder(this) + .SetPackageName("com.example.app") + .AddContents(manifest_contents) + .Build(); + + auto app_link_args = LinkCommandBuilder(this) + .SetManifestFile(app_manifest) + .AddParameter("-I", android_apk) + .AddParameter("--feature-flags", "flag="); + + const std::string app_apk = GetTestPath("app.apk"); + BuildApk({}, app_apk, std::move(app_link_args), this, &diag); + + // Permission element should be kept if > UDC, regardless of flag value + auto apk = LoadedApk::LoadApkFromPath(app_apk, &diag); + ASSERT_THAT(apk, NotNull()); + auto apk_manifest = apk->GetManifest(); + ASSERT_THAT(apk_manifest, NotNull()); + auto root = apk_manifest->root.get(); + ASSERT_THAT(root, NotNull()); + auto maybe_removed = root->FindChild({}, "permission"); + ASSERT_THAT(maybe_removed, NotNull()); +} + } // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index 8c644cf83339..a7f6f5524e21 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -64,29 +64,31 @@ static std::array<AnnotationRule, 3> sAnnotationRules = {{ {"@FlaggedApi", AnnotationRule::kFlaggedApi, "@android.annotation.FlaggedApi", true}, }}; -void AnnotationProcessor::AppendCommentLine(std::string comment) { +void AnnotationProcessor::AppendCommentLine(std::string comment, bool add_api_annotations) { static constexpr std::string_view sDeprecated = "@deprecated"; - // Treat deprecated specially, since we don't remove it from the source comment. - if (comment.find(sDeprecated) != std::string::npos) { - annotation_parameter_map_[AnnotationRule::kDeprecated] = ""; - } + if (add_api_annotations) { + // Treat deprecated specially, since we don't remove it from the source comment. + if (comment.find(sDeprecated) != std::string::npos) { + annotation_parameter_map_[AnnotationRule::kDeprecated] = ""; + } - for (const AnnotationRule& rule : sAnnotationRules) { - std::string::size_type idx = comment.find(rule.doc_str.data()); - if (idx != std::string::npos) { - // Captures all parameters associated with the specified annotation rule - // by matching the first pair of parentheses after the rule. - std::regex re(std::string(rule.doc_str).append(R"(\s*\((.+)\))")); - std::smatch match_result; - const bool is_match = std::regex_search(comment, match_result, re); - if (is_match && rule.preserve_params) { - annotation_parameter_map_[rule.bit_mask] = match_result[1].str(); - comment.erase(comment.begin() + match_result.position(), - comment.begin() + match_result.position() + match_result.length()); - } else { - annotation_parameter_map_[rule.bit_mask] = ""; - comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size()); + for (const AnnotationRule& rule : sAnnotationRules) { + std::string::size_type idx = comment.find(rule.doc_str.data()); + if (idx != std::string::npos) { + // Captures all parameters associated with the specified annotation rule + // by matching the first pair of parentheses after the rule. + std::regex re(std::string(rule.doc_str).append(R"(\s*\((.+)\))")); + std::smatch match_result; + const bool is_match = std::regex_search(comment, match_result, re); + if (is_match && rule.preserve_params) { + annotation_parameter_map_[rule.bit_mask] = match_result[1].str(); + comment.erase(comment.begin() + match_result.position(), + comment.begin() + match_result.position() + match_result.length()); + } else { + annotation_parameter_map_[rule.bit_mask] = ""; + comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size()); + } } } } @@ -109,12 +111,12 @@ void AnnotationProcessor::AppendCommentLine(std::string comment) { comment_ << "\n * " << std::move(comment); } -void AnnotationProcessor::AppendComment(StringPiece comment) { +void AnnotationProcessor::AppendComment(StringPiece comment, bool add_api_annotations) { // We need to process line by line to clean-up whitespace and append prefixes. for (StringPiece line : util::Tokenize(comment, '\n')) { line = util::TrimWhitespace(line); if (!line.empty()) { - AppendCommentLine(std::string(line)); + AppendCommentLine(std::string(line), add_api_annotations); } } } diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index db3437e3b5b1..2217ab35705a 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -60,7 +60,10 @@ class AnnotationProcessor { // Adds more comments. Resources can have value definitions for various configurations, and // each of the definitions may have comments that need to be processed. - void AppendComment(android::StringPiece comment); + // + // If add_api_annotations is false, annotations found in the comment (e.g., "@SystemApi") + // will NOT be converted to Java annotations. + void AppendComment(android::StringPiece comment, bool add_api_annotations = true); void AppendNewLine(); @@ -73,7 +76,7 @@ class AnnotationProcessor { bool has_comments_ = false; std::unordered_map<uint32_t, std::string> annotation_parameter_map_; - void AppendCommentLine(std::string line); + void AppendCommentLine(std::string line, bool add_api_annotations); }; } // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp index e98e96ba3bc3..e5eee34f451c 100644 --- a/tools/aapt2/java/AnnotationProcessor_test.cpp +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -136,7 +136,28 @@ TEST(AnnotationProcessorTest, NotEmitSystemApiAnnotation) { EXPECT_THAT(annotations, HasSubstr("This is a system API")); } -TEST(AnnotationProcessor, ExtractsFirstSentence) { +TEST(AnnotationProcessorTest, DoNotAddApiAnnotations) { + AnnotationProcessor processor; + processor.AppendComment( + "@SystemApi This is a system API\n" + "@FlaggedApi This is a flagged API\n" + "@TestApi This is a test API\n" + "@deprecated Deprecate me\n", /*add_api_annotations=*/ + false); + + std::string annotations; + StringOutputStream out(&annotations); + Printer printer(&out); + processor.Print(&printer); + out.Flush(); + + EXPECT_THAT(annotations, Not(HasSubstr("@android.annotation.SystemApi"))); + EXPECT_THAT(annotations, Not(HasSubstr("@android.annotation.FlaggedApi"))); + EXPECT_THAT(annotations, Not(HasSubstr("@android.annotation.TestApi"))); + EXPECT_THAT(annotations, Not(HasSubstr("@Deprecated"))); +} + +TEST(AnnotationProcessorTest, ExtractsFirstSentence) { EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"), Eq("This is the only sentence")); EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence( diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 58f656458177..6e73b017cce2 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -180,7 +180,10 @@ static void AddAttributeFormatDoc(AnnotationProcessor* processor, Attribute* att << "<td>" << std::hex << symbol.value << std::dec << "</td>" << "<td>" << util::TrimWhitespace(symbol.symbol.GetComment()) << "</td></tr>"; - processor->AppendComment(line.str()); + // add_api_annotations is false since we don't want any annotations + // (e.g., "@deprecated")/ found in the enum/flag values to be propagated + // up to the attribute. + processor->AppendComment(line.str(), /*add_api_annotations=*/false); } processor->AppendComment("</table>"); } diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 40395ed64fe3..bca9f4bc58c4 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -324,7 +324,58 @@ TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { EXPECT_THAT(output, HasSubstr(expected_text)); } -TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {} +TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) { + std::unique_ptr<Attribute> flagAttr = + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_FLAGS) + .SetComment("Flag attribute") + .AddItemWithComment("flagOne", 0x01, "Flag comment 1") + .AddItemWithComment("flagTwo", 0x02, "@deprecated Flag comment 2") + .Build(); + std::unique_ptr<Attribute> enumAttr = + test::AttributeBuilder() + .SetTypeMask(android::ResTable_map::TYPE_ENUM) + .SetComment("Enum attribute") + .AddItemWithComment("enumOne", 0x01, "@TestApi Enum comment 1") + .AddItemWithComment("enumTwo", 0x02, "Enum comment 2") + .Build(); + + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .AddValue("android:attr/one", std::move(flagAttr)) + .AddValue("android:attr/two", std::move(enumAttr)) + .Build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) + .SetNameManglerPolicy(NameManglerPolicy{"android"}) + .Build(); + JavaClassGeneratorOptions options; + options.use_final = false; + JavaClassGenerator generator(context.get(), table.get(), options); + + std::string output; + StringOutputStream out(&output); + ASSERT_TRUE(generator.Generate("android", &out)); + out.Flush(); + + // Special annotations from the enum/flag values should NOT generate + // annotations for the attribute value. + EXPECT_THAT(output, Not(HasSubstr("@Deprecated"))); + EXPECT_THAT(output, Not(HasSubstr("@android.annotation.TestApi"))); + + EXPECT_THAT(output, HasSubstr("Flag attribute")); + EXPECT_THAT(output, HasSubstr("flagOne")); + EXPECT_THAT(output, HasSubstr("Flag comment 1")); + EXPECT_THAT(output, HasSubstr("flagTwo")); + EXPECT_THAT(output, HasSubstr("@deprecated Flag comment 2")); + + EXPECT_THAT(output, HasSubstr("Enum attribute")); + EXPECT_THAT(output, HasSubstr("enumOne")); + EXPECT_THAT(output, HasSubstr("@TestApi Enum comment 1")); + EXPECT_THAT(output, HasSubstr("enumTwo")); + EXPECT_THAT(output, HasSubstr("Enum comment 2")); +} TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) { Attribute attr; diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp index 65f63dc68e54..b5934e40a2a3 100644 --- a/tools/aapt2/test/Builders.cpp +++ b/tools/aapt2/test/Builders.cpp @@ -177,12 +177,25 @@ AttributeBuilder& AttributeBuilder::SetWeak(bool weak) { return *this; } +AttributeBuilder& AttributeBuilder::SetComment(StringPiece comment) { + attr_->SetComment(comment); + return *this; +} + AttributeBuilder& AttributeBuilder::AddItem(StringPiece name, uint32_t value) { attr_->symbols.push_back( Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value}); return *this; } +AttributeBuilder& AttributeBuilder::AddItemWithComment(StringPiece name, uint32_t value, + StringPiece comment) { + Reference ref(ResourceName({}, ResourceType::kId, name)); + ref.SetComment(comment); + attr_->symbols.push_back(Attribute::Symbol{ref, value}); + return *this; +} + std::unique_ptr<Attribute> AttributeBuilder::Build() { return std::move(attr_); } diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 098535d8526f..9ee44ba6d04c 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -116,7 +116,10 @@ class AttributeBuilder { AttributeBuilder(); AttributeBuilder& SetTypeMask(uint32_t typeMask); AttributeBuilder& SetWeak(bool weak); + AttributeBuilder& SetComment(android::StringPiece comment); AttributeBuilder& AddItem(android::StringPiece name, uint32_t value); + AttributeBuilder& AddItemWithComment(android::StringPiece name, uint32_t value, + android::StringPiece comment); std::unique_ptr<Attribute> Build(); private: |