summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp14
-rw-r--r--core/java/android/window/flags/OWNERS1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java8
-rw-r--r--media/java/android/media/tv/flags/media_tv.aconfig8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java264
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt90
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt252
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt33
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt33
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java9
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceUtils.java28
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java23
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java4
-rw-r--r--services/tests/servicestests/res/xml/user_100_v9.xml20
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java130
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java56
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java2
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt6
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt172
-rw-r--r--tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt151
-rw-r--r--tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt4
-rw-r--r--tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt6
26 files changed, 853 insertions, 520 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2457e707ef57..372ae6da4959 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -53,6 +53,7 @@ aconfig_srcjars = [
":android.credentials.flags-aconfig-java{.generated_srcjars}",
":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+ ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
":aconfig_midi_flags_java_lib{.generated_srcjars}",
":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
":com.android.net.flags-aconfig-java{.generated_srcjars}",
@@ -379,6 +380,19 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+// Media TV
+aconfig_declarations {
+ name: "android.media.tv.flags-aconfig",
+ package: "android.media.tv.flags",
+ srcs: ["media/java/android/media/tv/flags/media_tv.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.media.tv.flags-aconfig-java",
+ aconfig_declarations: "android.media.tv.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
// Media Audio
java_aconfig_library {
name: "com.android.media.audio.flags-aconfig-java",
diff --git a/core/java/android/window/flags/OWNERS b/core/java/android/window/flags/OWNERS
new file mode 100644
index 000000000000..fa81ee3905c3
--- /dev/null
+++ b/core/java/android/window/flags/OWNERS
@@ -0,0 +1 @@
+per-file responsible_apis.aconfig = file:/BAL_OWNERS \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index b1fc16ddf19b..030f601a7b60 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -44,7 +44,7 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
private IBinder mTransition = null;
/** The remote to delegate animation to */
- private final RemoteTransition mRemote;
+ private RemoteTransition mRemote;
public OneShotRemoteHandler(@NonNull ShellExecutor mainExecutor,
@NonNull RemoteTransition remote) {
@@ -83,6 +83,8 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
mMainExecutor.execute(() -> {
finishCallback.onTransitionFinished(wct);
});
+ Log.d("b/302551868", "OneShotRemoteHandler#start remote anim null");
+ mRemote = null;
}
};
Transitions.setRunningRemoteTransitionDelegate(mRemote.getAppThread());
@@ -105,6 +107,8 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
}
finishCallback.onTransitionFinished(null /* wct */);
+ Log.d("b/302551868", "OneShotRemoteHandler#exception remote anim null");
+ mRemote = null;
}
return true;
}
@@ -123,6 +127,8 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
// so just assume the worst-case and clear the local transaction.
t.clear();
mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
+ Log.d("b/302551868", "OneShotRemoteHandler#merge remote anim null");
+ mRemote = null;
}
};
try {
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
new file mode 100644
index 000000000000..a73d1ff72a17
--- /dev/null
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -0,0 +1,8 @@
+package: "android.media.tv.flags"
+
+flag {
+ name: "broadcast_visibility_types"
+ namespace: "media_tv"
+ description: "Constants for standardizing broadcast visibility types."
+ bug: "222402395"
+} \ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
deleted file mode 100644
index 117b48ff852d..000000000000
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeEnablerManager.java
+++ /dev/null
@@ -1,264 +0,0 @@
-/*
- * Copyright (C) 2017 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.settingslib.inputmethod;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.text.TextUtils;
-import android.view.inputmethod.InputMethodInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSubtype;
-
-import androidx.preference.Preference;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceFragment;
-import androidx.preference.PreferenceScreen;
-import androidx.preference.TwoStatePreference;
-
-import com.android.settingslib.R;
-
-import java.text.Collator;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-public class InputMethodAndSubtypeEnablerManager implements Preference.OnPreferenceChangeListener {
-
- private final PreferenceFragment mFragment;
-
- private boolean mHaveHardKeyboard;
- private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
- new HashMap<>();
- private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
- private InputMethodManager mImm;
- // TODO: Change mInputMethodInfoList to Map
- private List<InputMethodInfo> mInputMethodInfoList;
- private final Collator mCollator = Collator.getInstance();
-
- public InputMethodAndSubtypeEnablerManager(PreferenceFragment fragment) {
- mFragment = fragment;
- mImm = fragment.getContext().getSystemService(InputMethodManager.class);
-
- mInputMethodInfoList = mImm.getInputMethodList();
- }
-
- public void init(PreferenceFragment fragment, String targetImi, PreferenceScreen root) {
- final Configuration config = fragment.getResources().getConfiguration();
- mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
-
- for (final InputMethodInfo imi : mInputMethodInfoList) {
- // Add subtype preferences of this IME when it is specified or no IME is specified.
- if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
- addInputMethodSubtypePreferences(fragment, imi, root);
- }
- }
- }
-
- public void refresh(Context context, PreferenceFragment fragment) {
- // Refresh internal states in mInputMethodSettingValues to keep the latest
- // "InputMethodInfo"s and "InputMethodSubtype"s
- InputMethodSettingValuesWrapper
- .getInstance(context).refreshAllInputMethodAndSubtypes();
- InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(fragment, context.getContentResolver(),
- mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
- updateAutoSelectionPreferences();
- }
-
- public void save(Context context, PreferenceFragment fragment) {
- InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(fragment, context.getContentResolver(),
- mInputMethodInfoList, mHaveHardKeyboard);
- }
-
- @Override
- public boolean onPreferenceChange(final Preference pref, final Object newValue) {
- if (!(newValue instanceof Boolean)) {
- return true; // Invoke default behavior.
- }
- final boolean isChecking = (Boolean) newValue;
- for (final String imiId : mAutoSelectionPrefsMap.keySet()) {
- // An auto select subtype preference is changing.
- if (mAutoSelectionPrefsMap.get(imiId) == pref) {
- final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref;
- autoSelectionPref.setChecked(isChecking);
- // Enable or disable subtypes depending on the auto selection preference.
- setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked());
- return false;
- }
- }
- // A subtype preference is changing.
- if (pref instanceof InputMethodSubtypePreference) {
- final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref;
- subtypePref.setChecked(isChecking);
- if (!subtypePref.isChecked()) {
- // It takes care of the case where no subtypes are explicitly enabled then the auto
- // selection preference is going to be checked.
- updateAutoSelectionPreferences();
- }
- return false;
- }
- return true; // Invoke default behavior.
- }
-
- private void addInputMethodSubtypePreferences(PreferenceFragment fragment, InputMethodInfo imi,
- final PreferenceScreen root) {
- Context prefContext = fragment.getPreferenceManager().getContext();
-
- final int subtypeCount = imi.getSubtypeCount();
- if (subtypeCount <= 1) {
- return;
- }
- final String imiId = imi.getId();
- final PreferenceCategory keyboardSettingsCategory =
- new PreferenceCategory(prefContext);
- root.addPreference(keyboardSettingsCategory);
- final PackageManager pm = prefContext.getPackageManager();
- final CharSequence label = imi.loadLabel(pm);
-
- keyboardSettingsCategory.setTitle(label);
- keyboardSettingsCategory.setKey(imiId);
- // TODO: Use toggle Preference if images are ready.
- final TwoStatePreference autoSelectionPref =
- new SwitchWithNoTextPreference(prefContext);
- mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
- keyboardSettingsCategory.addPreference(autoSelectionPref);
- autoSelectionPref.setOnPreferenceChangeListener(this);
-
- final PreferenceCategory activeInputMethodsCategory =
- new PreferenceCategory(prefContext);
- activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
- root.addPreference(activeInputMethodsCategory);
-
- CharSequence autoSubtypeLabel = null;
- final ArrayList<Preference> subtypePreferences = new ArrayList<>();
- for (int index = 0; index < subtypeCount; ++index) {
- final InputMethodSubtype subtype = imi.getSubtypeAt(index);
- if (subtype.overridesImplicitlyEnabledSubtype()) {
- if (autoSubtypeLabel == null) {
- autoSubtypeLabel = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(
- subtype, prefContext, imi);
- }
- } else {
- final Preference subtypePref = new InputMethodSubtypePreference(
- prefContext, subtype, imi);
- subtypePreferences.add(subtypePref);
- }
- }
- subtypePreferences.sort((lhs, rhs) -> {
- if (lhs instanceof InputMethodSubtypePreference) {
- return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
- }
- return lhs.compareTo(rhs);
- });
- for (final Preference pref : subtypePreferences) {
- activeInputMethodsCategory.addPreference(pref);
- pref.setOnPreferenceChangeListener(this);
- InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
- }
- mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
- if (TextUtils.isEmpty(autoSubtypeLabel)) {
- autoSelectionPref.setTitle(
- R.string.use_system_language_to_select_input_method_subtypes);
- } else {
- autoSelectionPref.setTitle(autoSubtypeLabel);
- }
- }
-
- private boolean isNoSubtypesExplicitlySelected(final String imiId) {
- final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
- for (final Preference pref : subtypePrefs) {
- if (pref instanceof TwoStatePreference && ((TwoStatePreference) pref).isChecked()) {
- return false;
- }
- }
- return true;
- }
-
- private void setAutoSelectionSubtypesEnabled(final String imiId,
- final boolean autoSelectionEnabled) {
- final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
- if (autoSelectionPref == null) {
- return;
- }
- autoSelectionPref.setChecked(autoSelectionEnabled);
- final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
- for (final Preference pref : subtypePrefs) {
- if (pref instanceof TwoStatePreference) {
- // When autoSelectionEnabled is true, all subtype prefs need to be disabled with
- // implicitly checked subtypes. In case of false, all subtype prefs need to be
- // enabled.
- pref.setEnabled(!autoSelectionEnabled);
- if (autoSelectionEnabled) {
- ((TwoStatePreference) pref).setChecked(false);
- }
- }
- }
- if (autoSelectionEnabled) {
- InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
- mFragment, mFragment.getContext().getContentResolver(),
- mInputMethodInfoList, mHaveHardKeyboard);
- updateImplicitlyEnabledSubtypes(imiId);
- }
- }
-
- private void updateImplicitlyEnabledSubtypes(final String targetImiId) {
- // When targetImiId is null, apply to all subtypes of all IMEs
- for (final InputMethodInfo imi : mInputMethodInfoList) {
- final String imiId = imi.getId();
- final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
- // No need to update implicitly enabled subtypes when the user has unchecked the
- // "subtype auto selection".
- if (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
- continue;
- }
- if (imiId.equals(targetImiId) || targetImiId == null) {
- updateImplicitlyEnabledSubtypesOf(imi);
- }
- }
- }
-
- private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi) {
- final String imiId = imi.getId();
- final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
- final List<InputMethodSubtype> implicitlyEnabledSubtypes =
- mImm.getEnabledInputMethodSubtypeList(imi, true);
- if (subtypePrefs == null || implicitlyEnabledSubtypes == null) {
- return;
- }
- for (final Preference pref : subtypePrefs) {
- if (!(pref instanceof TwoStatePreference)) {
- continue;
- }
- final TwoStatePreference subtypePref = (TwoStatePreference) pref;
- subtypePref.setChecked(false);
- for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
- final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
- if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
- subtypePref.setChecked(true);
- break;
- }
- }
- }
- }
-
- private void updateAutoSelectionPreferences() {
- for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
- setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
- }
- updateImplicitlyEnabledSubtypes(null /* targetImiId */ /* check */);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
new file mode 100644
index 000000000000..75e04841d4b0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.interruption
+
+import android.database.ContentObserver
+import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Handler
+import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
+import android.provider.Settings.Global.HEADS_UP_OFF
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.settings.GlobalSettings
+
+class PeekDisabledSuppressor(
+ private val globalSettings: GlobalSettings,
+ private val headsUpManager: HeadsUpManager,
+ private val logger: NotificationInterruptLogger,
+ @Main private val mainHandler: Handler,
+) : VisualInterruptionCondition(types = setOf(PEEK), reason = "peek setting disabled") {
+ private var isEnabled = false
+
+ override fun shouldSuppress(): Boolean = !isEnabled
+
+ override fun start() {
+ val observer =
+ object : ContentObserver(mainHandler) {
+ override fun onChange(selfChange: Boolean) {
+ val wasEnabled = isEnabled
+
+ isEnabled =
+ globalSettings.getInt(HEADS_UP_NOTIFICATIONS_ENABLED, HEADS_UP_OFF) !=
+ HEADS_UP_OFF
+
+ // QQQ: Do we want to log this even if it hasn't changed?
+ logger.logHeadsUpFeatureChanged(isEnabled)
+
+ // QQQ: Is there a better place for this side effect? What if HeadsUpManager
+ // registered for it directly?
+ if (wasEnabled && !isEnabled) {
+ logger.logWillDismissAll()
+ headsUpManager.releaseAllImmediately()
+ }
+ }
+ }
+
+ globalSettings.registerContentObserver(
+ globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED),
+ /* notifyForDescendants = */ true,
+ observer
+ )
+
+ // QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused.
+
+ observer.onChange(/* selfChange = */ true)
+ }
+}
+
+class PulseDisabledSuppressor(
+ private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+ private val userTracker: UserTracker,
+) : VisualInterruptionCondition(types = setOf(PULSE), reason = "pulse setting disabled") {
+ override fun shouldSuppress(): Boolean =
+ !ambientDisplayConfiguration.pulseOnNotificationEnabled(userTracker.userId)
+}
+
+class PulseBatterySaverSuppressor(private val batteryController: BatteryController) :
+ VisualInterruptionCondition(
+ types = setOf(PULSE),
+ reason = "pulsing disabled by battery saver"
+ ) {
+ override fun shouldSuppress() = batteryController.isAodPowerSave()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
index 7ead4bf3ed66..da8474e92629 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProvider.kt
@@ -52,6 +52,13 @@ interface VisualInterruptionDecisionProvider {
}
/**
+ * Initializes the provider.
+ *
+ * Must be called before any method except [addLegacySuppressor].
+ */
+ fun start() {}
+
+ /**
* Adds a [component][suppressor] that can suppress visual interruptions.
*
* This class may call suppressors in any order.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
new file mode 100644
index 000000000000..bae713486be5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.interruption
+
+import android.hardware.display.AmbientDisplayConfiguration
+import android.os.Handler
+import android.os.PowerManager
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.Decision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider.FullScreenIntentDecision
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+
+class VisualInterruptionDecisionProviderImpl
+@Inject
+constructor(
+ private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+ private val batteryController: BatteryController,
+ private val globalSettings: GlobalSettings,
+ private val headsUpManager: HeadsUpManager,
+ private val logger: NotificationInterruptLogger,
+ @Main private val mainHandler: Handler,
+ private val powerManager: PowerManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val systemClock: SystemClock,
+ private val userTracker: UserTracker,
+) : VisualInterruptionDecisionProvider {
+ private var started = false
+
+ override fun start() {
+ check(!started)
+
+ addCondition(PeekDisabledSuppressor(globalSettings, headsUpManager, logger, mainHandler))
+ addCondition(PulseDisabledSuppressor(ambientDisplayConfiguration, userTracker))
+ addCondition(PulseBatterySaverSuppressor(batteryController))
+
+ started = true
+ }
+
+ private class DecisionImpl(
+ override val shouldInterrupt: Boolean,
+ override val logReason: String
+ ) : Decision
+
+ private class FullScreenIntentDecisionImpl(
+ override val shouldInterrupt: Boolean,
+ override val wouldInterruptWithoutDnd: Boolean,
+ override val logReason: String,
+ val originalEntry: NotificationEntry,
+ ) : FullScreenIntentDecision {
+ var hasBeenLogged = false
+ }
+
+ private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>()
+ private val conditions = mutableListOf<VisualInterruptionCondition>()
+ private val filters = mutableListOf<VisualInterruptionFilter>()
+
+ override fun addLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
+ legacySuppressors.add(suppressor)
+ }
+
+ override fun removeLegacySuppressor(suppressor: NotificationInterruptSuppressor) {
+ legacySuppressors.remove(suppressor)
+ }
+
+ fun addCondition(condition: VisualInterruptionCondition) {
+ conditions.add(condition)
+ condition.start()
+ }
+
+ fun addFilter(filter: VisualInterruptionFilter) {
+ filters.add(filter)
+ filter.start()
+ }
+
+ override fun makeUnloggedHeadsUpDecision(entry: NotificationEntry): Decision {
+ check(started)
+ return makeHeadsUpDecision(entry)
+ }
+
+ override fun makeAndLogHeadsUpDecision(entry: NotificationEntry): Decision {
+ check(started)
+ return makeHeadsUpDecision(entry).also { logHeadsUpDecision(entry, it) }
+ }
+
+ override fun makeUnloggedFullScreenIntentDecision(
+ entry: NotificationEntry
+ ): FullScreenIntentDecision {
+ check(started)
+ return makeFullScreenDecision(entry)
+ }
+
+ override fun logFullScreenIntentDecision(decision: FullScreenIntentDecision) {
+ check(started)
+ val decisionImpl =
+ decision as? FullScreenIntentDecisionImpl
+ ?: run {
+ Log.wtf(TAG, "Wrong subclass of FullScreenIntentDecision: $decision")
+ return
+ }
+ if (decision.hasBeenLogged) {
+ Log.wtf(TAG, "Already logged decision: $decision")
+ return
+ }
+ logFullScreenIntentDecision(decisionImpl)
+ decision.hasBeenLogged = true
+ }
+
+ override fun makeAndLogBubbleDecision(entry: NotificationEntry): Decision {
+ check(started)
+ return makeBubbleDecision(entry).also { logBubbleDecision(entry, it) }
+ }
+
+ private fun makeHeadsUpDecision(entry: NotificationEntry): DecisionImpl {
+ if (statusBarStateController.isDozing) {
+ return makePulseDecision(entry)
+ } else {
+ return makePeekDecision(entry)
+ }
+ }
+
+ private fun makePeekDecision(entry: NotificationEntry): DecisionImpl {
+ checkConditions(PEEK)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkFilters(PEEK, entry)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressInterruptions"
+ )
+ }
+ checkAwakeSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressAwakeInterruptions"
+ )
+ }
+ checkAwakeHeadsUpSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressAwakeHeadsUpInterruptions"
+ )
+ }
+ return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
+ }
+
+ private fun makePulseDecision(entry: NotificationEntry): DecisionImpl {
+ checkConditions(PULSE)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkFilters(PULSE, entry)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressInterruptions"
+ )
+ }
+ return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
+ }
+
+ private fun makeBubbleDecision(entry: NotificationEntry): DecisionImpl {
+ checkConditions(BUBBLE)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkFilters(BUBBLE, entry)?.let {
+ return DecisionImpl(shouldInterrupt = false, logReason = it.reason)
+ }
+ checkSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressInterruptions"
+ )
+ }
+ checkAwakeSuppressors(entry)?.let {
+ return DecisionImpl(
+ shouldInterrupt = false,
+ logReason = "${it.name}.suppressAwakeInterruptions"
+ )
+ }
+ return DecisionImpl(shouldInterrupt = true, logReason = "not suppressed")
+ }
+
+ private fun makeFullScreenDecision(entry: NotificationEntry): FullScreenIntentDecisionImpl {
+ // Not yet implemented.
+ return FullScreenIntentDecisionImpl(
+ shouldInterrupt = true,
+ wouldInterruptWithoutDnd = true,
+ logReason = "FSI logic not yet implemented in VisualInterruptionDecisionProviderImpl",
+ originalEntry = entry
+ )
+ }
+
+ private fun logHeadsUpDecision(entry: NotificationEntry, decision: DecisionImpl) {
+ // Not yet implemented.
+ }
+
+ private fun logBubbleDecision(entry: NotificationEntry, decision: DecisionImpl) {
+ // Not yet implemented.
+ }
+
+ private fun logFullScreenIntentDecision(decision: FullScreenIntentDecisionImpl) {
+ // Not yet implemented.
+ }
+
+ private fun checkSuppressors(entry: NotificationEntry) =
+ legacySuppressors.firstOrNull { it.suppressInterruptions(entry) }
+
+ private fun checkAwakeSuppressors(entry: NotificationEntry) =
+ legacySuppressors.firstOrNull { it.suppressAwakeInterruptions(entry) }
+
+ private fun checkAwakeHeadsUpSuppressors(entry: NotificationEntry) =
+ legacySuppressors.firstOrNull { it.suppressAwakeHeadsUp(entry) }
+
+ private fun checkConditions(type: VisualInterruptionType): VisualInterruptionCondition? =
+ conditions.firstOrNull { it.types.contains(type) && it.shouldSuppress() }
+
+ private fun checkFilters(
+ type: VisualInterruptionType,
+ entry: NotificationEntry
+ ): VisualInterruptionFilter? =
+ filters.firstOrNull { it.types.contains(type) && it.shouldSuppress(entry) }
+}
+
+private const val TAG = "VisualInterruptionDecisionProviderImpl"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
index d524637443cb..39199df37bd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionSuppressor.kt
@@ -51,6 +51,12 @@ sealed interface VisualInterruptionSuppressor {
/** An optional UiEvent ID to be recorded when this suppresses an interruption. */
val uiEventId: UiEventEnum?
+
+ /**
+ * Called after the suppressor is added to the [VisualInterruptionDecisionProvider] but before
+ * any other methods are called on the suppressor.
+ */
+ fun start() {}
}
/** A reason why visual interruptions might be suppressed regardless of the notification. */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
index 21de73ac4c7b..1d2055ec2e30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt
@@ -38,23 +38,22 @@ class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecision
override val provider by lazy {
NotificationInterruptStateProviderWrapper(
NotificationInterruptStateProviderImpl(
- powerManager,
- ambientDisplayConfiguration,
- batteryController,
- statusBarStateController,
- keyguardStateController,
- headsUpManager,
- logger,
- mainHandler,
- flags,
- keyguardNotificationVisibilityProvider,
- uiEventLogger,
- userTracker,
- deviceProvisionedController,
- systemClock,
- globalSettings,
- )
- .also { it.mUseHeadsUp = true }
+ powerManager,
+ ambientDisplayConfiguration,
+ batteryController,
+ statusBarStateController,
+ keyguardStateController,
+ headsUpManager,
+ logger,
+ mainHandler,
+ flags,
+ keyguardNotificationVisibilityProvider,
+ uiEventLogger,
+ userTracker,
+ deviceProvisionedController,
+ systemClock,
+ globalSettings,
+ )
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
new file mode 100644
index 000000000000..ff89bdb6dbde
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.interruption
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() {
+ override val provider by lazy {
+ VisualInterruptionDecisionProviderImpl(
+ ambientDisplayConfiguration,
+ batteryController,
+ globalSettings,
+ headsUpManager,
+ logger,
+ mainHandler,
+ powerManager,
+ statusBarStateController,
+ systemClock,
+ userTracker,
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index c0aaa3670ace..551119436c7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -30,9 +30,10 @@ import android.content.Intent
import android.content.pm.UserInfo
import android.graphics.drawable.Icon
import android.hardware.display.FakeAmbientDisplayConfiguration
-import android.os.Handler
+import android.os.Looper
import android.os.PowerManager
import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
+import android.provider.Settings.Global.HEADS_UP_OFF
import android.provider.Settings.Global.HEADS_UP_ON
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -54,6 +55,7 @@ import com.android.systemui.util.settings.FakeGlobalSettings
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.utils.leaks.FakeBatteryController
import com.android.systemui.utils.leaks.LeakCheckedTest
+import com.android.systemui.utils.os.FakeHandler
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
@@ -73,7 +75,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
mock()
protected val keyguardStateController: KeyguardStateController = mock()
protected val logger: NotificationInterruptLogger = mock()
- protected val mainHandler: Handler = mock()
+ protected val mainHandler = FakeHandler(Looper.getMainLooper())
protected val powerManager: PowerManager = mock()
protected val statusBarStateController = FakeStatusBarStateController()
protected val systemClock = FakeSystemClock()
@@ -108,6 +110,8 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
whenever(keyguardNotificationVisibilityProvider.shouldHideNotification(any()))
.thenReturn(false)
+
+ provider.start()
}
@Test
@@ -117,6 +121,12 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
}
@Test
+ fun testShouldNotPeek_settingDisabled() {
+ ensurePeekState { hunSettingEnabled = false }
+ assertShouldNotHeadsUp(buildPeekEntry())
+ }
+
+ @Test
fun testShouldPeek_defaultLegacySuppressor() {
ensurePeekState()
provider.addLegacySuppressor(neverSuppresses)
@@ -179,6 +189,18 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
}
@Test
+ fun testShouldNotPulse_disabled() {
+ ensurePulseState { pulseOnNotificationsEnabled = false }
+ assertShouldNotHeadsUp(buildPulseEntry())
+ }
+
+ @Test
+ fun testShouldNotPulse_batterySaver() {
+ ensurePulseState { isAodPowerSave = true }
+ assertShouldNotHeadsUp(buildPulseEntry())
+ }
+
+ @Test
fun testShouldBubble() {
ensureBubbleState()
assertShouldBubble(buildBubbleEntry())
@@ -231,6 +253,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
}
private data class State(
+ var hunSettingEnabled: Boolean? = null,
var hunSnoozed: Boolean? = null,
var isAodPowerSave: Boolean? = null,
var isDozing: Boolean? = null,
@@ -244,6 +267,11 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
private fun setState(state: State): Unit =
state.run {
+ hunSettingEnabled?.let {
+ val newSetting = if (it) HEADS_UP_ON else HEADS_UP_OFF
+ globalSettings.putInt(HEADS_UP_NOTIFICATIONS_ENABLED, newSetting)
+ }
+
hunSnoozed?.let { whenever(headsUpManager.isSnoozed(TEST_PACKAGE)).thenReturn(it) }
isAodPowerSave?.let { batteryController.setIsAodPowerSave(it) }
@@ -277,6 +305,7 @@ abstract class VisualInterruptionDecisionProviderTestBase : SysuiTestCase() {
.run(this::setState)
private fun ensurePeekState(block: State.() -> Unit = {}) = ensureState {
+ hunSettingEnabled = true
hunSnoozed = false
isDozing = false
isDreaming = false
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 32666e76372b..bb50a991d9c1 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3406,7 +3406,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// displays in one display. It's not a real display and there's no input events for it.
final ArrayList<Display> displays = getValidDisplayList();
if (userState.isMagnificationSingleFingerTripleTapEnabledLocked()
- || userState.isMagnificationTwoFingerTripleTapEnabledLocked()
+ || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+ && userState.isMagnificationTwoFingerTripleTapEnabledLocked())
|| userState.isShortcutMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
@@ -3435,7 +3436,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
return;
}
final boolean connect = (userState.isShortcutMagnificationEnabledLocked()
- || userState.isMagnificationSingleFingerTripleTapEnabledLocked())
+ || userState.isMagnificationSingleFingerTripleTapEnabledLocked()
+ || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+ && userState.isMagnificationTwoFingerTripleTapEnabledLocked()))
&& (userState.getMagnificationCapabilitiesLocked()
!= Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN)
|| userHasMagnificationServicesLocked(userState);
@@ -5133,6 +5136,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// Remove magnification button UI when the magnification capability is not all mode or
// magnification is disabled.
if (!(userState.isMagnificationSingleFingerTripleTapEnabledLocked()
+ || (Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+ && userState.isMagnificationTwoFingerTripleTapEnabledLocked())
|| userState.isShortcutMagnificationEnabledLocked())
|| userState.getMagnificationCapabilitiesLocked()
!= Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 2951ef630eb3..40d3ef03395d 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1867,8 +1867,7 @@ final class InstallPackageHelper {
final File targetDir = resolveTargetDir(request.getInstallFlags(), request.getCodeFile());
final File beforeCodeFile = request.getCodeFile();
- final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir,
- parsedPackage.getPackageName());
+ final File afterCodeFile = PackageManagerServiceUtils.getNextCodePath(targetDir);
if (DEBUG_INSTALL) Slog.d(TAG, "Renaming " + beforeCodeFile + " to " + afterCodeFile);
final boolean onIncremental = mPm.mIncrementalManager != null
@@ -3099,8 +3098,7 @@ final class InstallPackageHelper {
return null;
}
final File dstCodePath =
- PackageManagerServiceUtils.getNextCodePath(Environment.getDataAppDirectory(null),
- packageName);
+ PackageManagerServiceUtils.getNextCodePath(Environment.getDataAppDirectory(null));
int ret = PackageManagerServiceUtils.decompressFiles(codePath, dstCodePath, packageName);
if (ret == PackageManager.INSTALL_SUCCEEDED) {
ret = PackageManagerServiceUtils.extractNativeBinaries(dstCodePath, packageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index bcb7bdebf9bf..f8e909e4c112 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -30,7 +30,6 @@ import static com.android.server.pm.PackageManagerService.COMPRESSED_EXTENSION;
import static com.android.server.pm.PackageManagerService.DEBUG_COMPRESSION;
import static com.android.server.pm.PackageManagerService.DEBUG_INTENT_MATCHING;
import static com.android.server.pm.PackageManagerService.DEBUG_PREFERRED;
-import static com.android.server.pm.PackageManagerService.RANDOM_CODEPATH_PREFIX;
import static com.android.server.pm.PackageManagerService.RANDOM_DIR_PREFIX;
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.STUB_SUFFIX;
@@ -1299,16 +1298,15 @@ public class PackageManagerServiceUtils {
}
/**
- * Given {@code targetDir}, returns {@code targetDir/~~[randomStrA]/[packageName]-[randomStrB].}
+ * Given {@code targetDir}, returns {@code targetDir/~~[randomStrA]/[randomStrB].}
* Makes sure that {@code targetDir/~~[randomStrA]} directory doesn't exist.
* Notice that this method doesn't actually create any directory.
*
* @param targetDir Directory that is two-levels up from the result directory.
- * @param packageName Name of the package whose code files are to be installed under the result
- * directory.
- * @return File object for the directory that should hold the code files of {@code packageName}.
+ *
+ * @return File object for the directory that should hold the code files.
*/
- public static File getNextCodePath(File targetDir, String packageName) {
+ public static File getNextCodePath(File targetDir) {
SecureRandom random = new SecureRandom();
byte[] bytes = new byte[16];
File firstLevelDir;
@@ -1320,22 +1318,8 @@ public class PackageManagerServiceUtils {
} while (firstLevelDir.exists());
random.nextBytes(bytes);
- String dirName = packageName + RANDOM_CODEPATH_PREFIX + Base64.encodeToString(bytes,
- Base64.URL_SAFE | Base64.NO_WRAP);
- final File result = new File(firstLevelDir, dirName);
- if (DEBUG && !Objects.equals(tryParsePackageName(result.getName()), packageName)) {
- throw new RuntimeException(
- "codepath is off: " + result.getName() + " (" + packageName + ")");
- }
- return result;
- }
-
- static String tryParsePackageName(@NonNull String codePath) throws IllegalArgumentException {
- int packageNameEnds = codePath.indexOf(RANDOM_CODEPATH_PREFIX);
- if (packageNameEnds == -1) {
- throw new IllegalArgumentException("Not a valid package folder name");
- }
- return codePath.substring(0, packageNameEnds);
+ String dirName = Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
+ return new File(firstLevelDir, dirName);
}
/**
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 534f17601b86..c97fbdad9bf4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -3836,7 +3836,8 @@ public class UserManagerService extends IUserManager.Stub {
if (type == XmlPullParser.START_TAG) {
final String name = parser.getName();
if (name.equals(TAG_USER)) {
- UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID));
+ UserData userData = readUserLP(parser.getAttributeInt(null, ATTR_ID),
+ mUserVersion);
if (userData != null) {
synchronized (mUsersLock) {
@@ -4555,7 +4556,7 @@ public class UserManagerService extends IUserManager.Stub {
}
@GuardedBy({"mPackagesLock"})
- private UserData readUserLP(int id) {
+ private UserData readUserLP(int id, int userVersion) {
try (ResilientAtomicFile file = getUserFile(id)) {
FileInputStream fis = null;
try {
@@ -4564,19 +4565,19 @@ public class UserManagerService extends IUserManager.Stub {
Slog.e(LOG_TAG, "User info not found, returning null, user id: " + id);
return null;
}
- return readUserLP(id, fis);
+ return readUserLP(id, fis, userVersion);
} catch (Exception e) {
// Remove corrupted file and retry.
Slog.e(LOG_TAG, "Error reading user info, user id: " + id);
file.failRead(fis, e);
- return readUserLP(id);
+ return readUserLP(id, userVersion);
}
}
}
@GuardedBy({"mPackagesLock"})
@VisibleForTesting
- UserData readUserLP(int id, InputStream is) throws IOException,
+ UserData readUserLP(int id, InputStream is, int userVersion) throws IOException,
XmlPullParserException {
int flags = 0;
String userType = null;
@@ -4669,7 +4670,17 @@ public class UserManagerService extends IUserManager.Stub {
} else if (TAG_DEVICE_POLICY_RESTRICTIONS.equals(tag)) {
legacyLocalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS.equals(tag)) {
- localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+ if (userVersion < 10) {
+ // Prior to version 10, the local user restrictions were stored as sub tags
+ // grouped by the user id of the source user. The source is no longer stored
+ // on versions 10+ as this is now stored in the DevicePolicyEngine.
+ RestrictionsSet oldLocalRestrictions =
+ RestrictionsSet.readRestrictions(
+ parser, TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS);
+ localRestrictions = oldLocalRestrictions.mergeAll();
+ } else {
+ localRestrictions = UserRestrictionsUtils.readRestrictions(parser);
+ }
} else if (TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS.equals(tag)) {
globalRestrictions = UserRestrictionsUtils.readRestrictions(parser);
} else if (TAG_GUEST_RESTRICTIONS.equals(tag)) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4a467dfbcd14..b1c1fd6e9d8c 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1681,8 +1681,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
return false;
}
- if (!StorageManager.isUserKeyUnlocked(mCurrentUser)) {
- // Can't launch home on secondary display areas if device is still locked.
+ if (!StorageManager.isCeStorageUnlocked(mCurrentUser)) {
+ // Can't launch home on secondary display areas if CE storage is still locked.
return false;
}
diff --git a/services/tests/servicestests/res/xml/user_100_v9.xml b/services/tests/servicestests/res/xml/user_100_v9.xml
new file mode 100644
index 000000000000..03c08ed40828
--- /dev/null
+++ b/services/tests/servicestests/res/xml/user_100_v9.xml
@@ -0,0 +1,20 @@
+<user id="100"
+ serialNumber="0"
+ flags="3091"
+ type="android.os.usertype.full.SYSTEM"
+ created="0"
+ lastLoggedIn="0"
+ lastLoggedInFingerprint="0"
+ profileBadge="0">
+ <restrictions no_oem_unlock="true" />
+ <device_policy_local_restrictions>
+ <restrictions_user user_id="0">
+ <restrictions no_camera="true" />
+ </restrictions_user>
+ <restrictions_user user_id="100">
+ <restrictions no_camera="true" />
+ <restrictions no_install_unknown_sources="true" />
+ </restrictions_user>
+ </device_policy_local_restrictions>
+ <ignorePrepareStorageErrors>false</ignorePrepareStorageErrors>
+</user> \ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index b9e45bab7ab3..82efdd3ce40a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -535,6 +535,78 @@ public class AccessibilityManagerServiceTest {
@SmallTest
@Test
+ public void testOnClientChange_magnificationTripleTapEnabled_requestConnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ userState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(true);
+ }
+
+ @SmallTest
+ @Test
+ public void testOnClientChange_magnificationTripleTapDisabled_requestDisconnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ //userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+ userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(false);
+ }
+
+ @SmallTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void testOnClientChange_magnificationTwoFingerTripleTapEnabled_requestConnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ userState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(true);
+ }
+
+ @SmallTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void testOnClientChange_magnificationTwoFingerTripleTapDisabled_requestDisconnection() {
+ when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
+
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(
+ Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ //userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+ userState.setMagnificationTwoFingerTripleTapEnabledLocked(false);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr).requestConnection(false);
+ }
+
+ @SmallTest
+ @Test
public void testOnClientChange_boundServiceCanControlMagnification_requestConnection() {
when(mProxyManager.canRetrieveInteractiveWindowsLocked()).thenReturn(false);
@@ -547,6 +619,64 @@ public class AccessibilityManagerServiceTest {
verify(mMockWindowMagnificationMgr).requestConnection(true);
}
+ @SmallTest
+ @Test
+ public void testOnClientChange_magnificationTripleTapDisabled_removeMagnificationButton() {
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ userState.setMagnificationSingleFingerTripleTapEnabledLocked(false);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt());
+ }
+
+ @SmallTest
+ @Test
+ public void testOnClientChange_magnificationTripleTapEnabled_keepMagnificationButton() {
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ userState.setMagnificationSingleFingerTripleTapEnabledLocked(true);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt());
+ }
+
+ @SmallTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void onClientChange_magnificationTwoFingerTripleTapDisabled_removeMagnificationButton() {
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
+ userState.setMagnificationTwoFingerTripleTapEnabledLocked(false);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr, atLeastOnce()).removeMagnificationButton(anyInt());
+ }
+
+ @SmallTest
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE)
+ public void onClientChange_magnificationTwoFingerTripleTapEnabled_keepMagnificationButton() {
+ final AccessibilityUserState userState = mA11yms.mUserStates.get(
+ mA11yms.getCurrentUserIdLocked());
+ userState.setMagnificationCapabilitiesLocked(ACCESSIBILITY_MAGNIFICATION_MODE_ALL);
+ userState.setMagnificationTwoFingerTripleTapEnabledLocked(true);
+
+ // Invokes client change to trigger onUserStateChanged.
+ mA11yms.onClientChangeLocked(/* serviceInfoChanged= */false);
+
+ verify(mMockWindowMagnificationMgr, never()).removeMagnificationButton(anyInt());
+ }
+
@Test
public void testUnbindIme_whenServiceUnbinds() {
setupAccessibilityServiceConnection(AccessibilityServiceInfo.FLAG_INPUT_METHOD_EDITOR);
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 253592c9a07d..d1b2e8e6d868 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -43,6 +43,7 @@ import android.annotation.UserIdInt;
import android.app.PropertyInvalidatedCache;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.res.Resources;
import android.multiuser.Flags;
import android.os.Looper;
import android.os.Parcel;
@@ -50,21 +51,26 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
import android.text.TextUtils;
+import android.util.Xml;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.frameworks.servicestests.R;
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerService.UserData;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
+import java.nio.charset.StandardCharsets;
import java.util.List;
/**
@@ -77,6 +83,7 @@ import java.util.List;
@MediumTest
public class UserManagerServiceUserInfoTest {
private UserManagerService mUserManagerService;
+ private Resources mResources;
@Before
public void setup() {
@@ -96,6 +103,8 @@ public class UserManagerServiceUserInfoTest {
assertEquals("Multiple users so this test can't run.", 1, users.size());
assertEquals("Only user present isn't the system user.",
UserHandle.USER_SYSTEM, users.get(0).id);
+
+ mResources = InstrumentationRegistry.getTargetContext().getResources();
}
@Test
@@ -109,7 +118,7 @@ public class UserManagerServiceUserInfoTest {
byte[] bytes = baos.toByteArray();
UserData read = mUserManagerService.readUserLP(
- data.info.id, new ByteArrayInputStream(bytes));
+ data.info.id, new ByteArrayInputStream(bytes), 0);
assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false);
}
@@ -146,11 +155,13 @@ public class UserManagerServiceUserInfoTest {
// Clear the restrictions to see if they are properly read in from the user file.
setUserRestrictions(data.info.id, globalRestriction, localRestriction, false);
+ final int userVersion = 10;
//read the secondary and SYSTEM user file to fetch local/global device policy restrictions.
- mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(secondaryUserBytes));
+ mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(secondaryUserBytes),
+ userVersion);
if (Flags.saveGlobalAndGuestRestrictionsOnSystemUserXmlReadOnly()) {
mUserManagerService.readUserLP(UserHandle.USER_SYSTEM,
- new ByteArrayInputStream(systemUserBytes));
+ new ByteArrayInputStream(systemUserBytes), userVersion);
}
assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction));
@@ -303,6 +314,45 @@ public class UserManagerServiceUserInfoTest {
assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO));
}
+ /** Tests readUserLP upgrading from version 9 to 10+. */
+ @Test
+ public void testUserRestrictionsUpgradeFromV9() throws Exception {
+ final String[] localRestrictions = new String[] {
+ UserManager.DISALLOW_CAMERA,
+ UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
+ };
+
+ final int userId = 100;
+ UserData data = new UserData();
+ data.info = createUser(userId, FLAG_FULL, "A type");
+
+ mUserManagerService.putUserInfo(data.info);
+
+ for (String restriction : localRestrictions) {
+ assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId));
+ assertFalse(mUserManagerService.hasUserRestriction(restriction, userId));
+ }
+
+ // Convert the xml resource to the system storage xml format.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ DataOutputStream os = new DataOutputStream(baos);
+ XmlPullParser in = mResources.getXml(R.xml.user_100_v9);
+ XmlSerializer out = Xml.newBinarySerializer();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+ Xml.copy(in, out);
+ byte[] userBytes = baos.toByteArray();
+ baos.reset();
+
+ final int userVersion = 9;
+ mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(userBytes),
+ userVersion);
+
+ for (String restriction : localRestrictions) {
+ assertFalse(mUserManagerService.hasBaseUserRestriction(restriction, userId));
+ assertTrue(mUserManagerService.hasUserRestriction(restriction, userId));
+ }
+ }
+
/** Creates a UserInfo with the given flags and userType. */
private UserInfo createUser(@UserIdInt int userId, @UserInfoFlag int flags, String userType) {
return new UserInfo(userId, "A Name", "A path", flags, userType);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index c241033c69d3..eb78906f570d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -872,7 +872,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
new TestDisplayContent.Builder(mAtm, 1000, 1500)
.setSystemDecorations(true).build();
- // Use invalid user id to let StorageManager.isUserKeyUnlocked() return false.
+ // Use invalid user id to let StorageManager.isCeStorageUnlocked() return false.
final int currentUser = mRootWindowContainer.mCurrentUser;
mRootWindowContainer.mCurrentUser = -1;
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
index a20266a9b140..28eab8f62e74 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/AndroidGlobalIssueRegistry.kt
@@ -20,7 +20,6 @@ import com.android.tools.lint.client.api.IssueRegistry
import com.android.tools.lint.client.api.Vendor
import com.android.tools.lint.detector.api.CURRENT_API
import com.google.android.lint.aidl.EnforcePermissionDetector
-import com.google.android.lint.aidl.EnforcePermissionHelperDetector
import com.google.android.lint.aidl.SimpleManualPermissionEnforcementDetector
import com.google.auto.service.AutoService
@@ -30,7 +29,8 @@ class AndroidGlobalIssueRegistry : IssueRegistry() {
override val issues = listOf(
EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION,
- EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+ EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+ EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION,
SimpleManualPermissionEnforcementDetector.ISSUE_SIMPLE_MANUAL_PERMISSION_ENFORCEMENT,
)
@@ -45,4 +45,4 @@ class AndroidGlobalIssueRegistry : IssueRegistry() {
feedbackUrl = "http://b/issues/new?component=315013",
contact = "repsonsible-apis@google.com"
)
-} \ No newline at end of file
+}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index 3a95df9b2773..dcd94f1bcba4 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -30,31 +30,34 @@ import com.android.tools.lint.detector.api.JavaContext
import com.android.tools.lint.detector.api.Scope
import com.android.tools.lint.detector.api.Severity
import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.google.android.lint.findCallExpression
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiArrayInitializerMemberValue
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
-import org.jetbrains.uast.UAnnotation
+import org.jetbrains.uast.UBlockExpression
+import org.jetbrains.uast.UDeclarationsExpression
import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.toUElement
+import org.jetbrains.uast.skipParenthesizedExprDown
import java.util.EnumSet
/**
- * Lint Detector that ensures that any method overriding a method annotated
- * with @EnforcePermission is also annotated with the exact same annotation.
- * The intent is to surface the effective permission checks to the service
- * implementations.
+ * Lint Detector that ensures consistency when using the @EnforcePermission
+ * annotation. Multiple verifications are implemented:
*
- * This is done with 2 mechanisms:
* 1. Visit any annotation usage, to ensure that any derived class will have
- * the correct annotation on each methods. This is for the top to bottom
- * propagation.
- * 2. Visit any annotation, to ensure that if a method is annotated, it has
+ * the correct annotation on each methods. Even if the subclass does not
+ * have the annotation, visitAnnotationUsage will be called which allows us
+ * to capture the issue.
+ * 2. Visit any method, to ensure that if a method is annotated, it has
* its ancestor also annotated. This is to avoid having an annotation on a
* Java method without the corresponding annotation on the AIDL interface.
+ * 3. When annotated, ensures that the first instruction is to call the helper
+ * method (or the parent helper).
*/
class EnforcePermissionDetector : Detector(), SourceCodeScanner {
@@ -62,9 +65,8 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
return listOf(ANNOTATION_ENFORCE_PERMISSION)
}
- override fun getApplicableUastTypes(): List<Class<out UElement>> {
- return listOf(UAnnotation::class.java)
- }
+ override fun getApplicableUastTypes(): List<Class<out UElement?>> =
+ listOf(UMethod::class.java)
private fun annotationValueGetChildren(elem: PsiElement): Array<PsiElement> {
if (elem is PsiArrayInitializerMemberValue)
@@ -129,11 +131,6 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
overriddenMethod: PsiMethod,
checkEquivalence: Boolean = true
) {
- // If method is not from a Stub subclass, this method shouldn't use @EP at all.
- // This is handled by EnforcePermissionHelperDetector.
- if (!isContainedInSubclassOfStub(context, overridingMethod.toUElement() as? UMethod)) {
- return
- }
val overridingAnnotation = overridingMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
val overriddenAnnotation = overriddenMethod.getAnnotation(ANNOTATION_ENFORCE_PERMISSION)
val location = context.getLocation(element)
@@ -169,40 +166,102 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
) {
if (usageInfo.type == AnnotationUsageType.METHOD_OVERRIDE &&
annotationInfo.origin == AnnotationOrigin.METHOD) {
+ /* Ignore implementations that are not a sub-class of Stub (i.e., Proxy). */
+ val uMethod = element as? UMethod ?: return
+ if (!isContainedInSubclassOfStub(context, uMethod)) {
+ return
+ }
val overridingMethod = element.sourcePsi as PsiMethod
val overriddenMethod = usageInfo.referenced as PsiMethod
compareMethods(context, element, overridingMethod, overriddenMethod)
}
}
- override fun createUastHandler(context: JavaContext): UElementHandler {
- return object : UElementHandler() {
- override fun visitAnnotation(node: UAnnotation) {
- if (node.qualifiedName != ANNOTATION_ENFORCE_PERMISSION) {
- return
- }
- val method = node.uastParent as? UMethod ?: return
- val overridingMethod = method as PsiMethod
- val parents = overridingMethod.findSuperMethods()
- for (overriddenMethod in parents) {
- // The equivalence check can be skipped, if both methods are
- // annotated, it will be verified by visitAnnotationUsage.
- compareMethods(context, method, overridingMethod,
- overriddenMethod, checkEquivalence = false)
- }
+ override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
+
+ private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
+ override fun visitMethod(node: UMethod) {
+ if (context.evaluator.isAbstract(node)) return
+ if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
+
+ if (!isContainedInSubclassOfStub(context, node)) {
+ context.report(
+ ISSUE_MISUSING_ENFORCE_PERMISSION,
+ node,
+ context.getLocation(node),
+ "The class of ${node.name} does not inherit from an AIDL generated Stub class"
+ )
+ return
+ }
+
+ /* Check that we are connected to the super class */
+ val overridingMethod = node as PsiMethod
+ val parents = overridingMethod.findSuperMethods()
+ for (overriddenMethod in parents) {
+ // The equivalence check can be skipped, if both methods are
+ // annotated, it will be verified by visitAnnotationUsage.
+ compareMethods(context, node, overridingMethod,
+ overriddenMethod, checkEquivalence = false)
+ }
+
+ /* Check that the helper is called as a first instruction */
+ val targetExpression = getHelperMethodCallSourceString(node)
+ val message =
+ "Method must start with $targetExpression or super.${node.name}(), if applicable"
+
+ val firstExpression = (node.uastBody as? UBlockExpression)
+ ?.expressions?.firstOrNull()
+
+ if (firstExpression == null) {
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ message,
+ )
+ return
+ }
+
+ val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
+ .asSourceString()
+ .filterNot(Char::isWhitespace)
+
+ if (firstExpressionSource != targetExpression &&
+ firstExpressionSource != "super.$targetExpression") {
+ // calling super.<methodName>() is also legal
+ val directSuper = context.evaluator.getSuperMethod(node)
+ val firstCall = findCallExpression(firstExpression)?.resolve()
+ if (directSuper != null && firstCall == directSuper) return
+
+ val locationTarget = getLocationTarget(firstExpression)
+ val expressionLocation = context.getLocation(locationTarget)
+
+ context.report(
+ ISSUE_ENFORCE_PERMISSION_HELPER,
+ context.getLocation(node),
+ message,
+ getHelperMethodFix(node, expressionLocation),
+ )
}
}
}
companion object {
+
+ private const val HELPER_SUFFIX = "_enforcePermission"
+
val EXPLANATION = """
- The @EnforcePermission annotation is used to indicate that the underlying binder code
- has already verified the caller's permissions before calling the appropriate method. The
- verification code is usually generated by the AIDL compiler, which also takes care of
- annotating the generated Java code.
+ The @EnforcePermission annotation is used to delegate the verification of the caller's
+ permissions to a generated AIDL method.
In order to surface that information to platform developers, the same annotation must be
used on the implementation class or methods.
+
+ The @EnforcePermission annotation can only be used on methods whose class extends from
+ the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
+ AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
+
+ yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
+ either call it directly, or call it indirectly via super.yourMethodName().
"""
val ISSUE_MISSING_ENFORCE_PERMISSION: Issue = Issue.create(
@@ -230,5 +289,44 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
)
)
+
+ val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
+ id = "MissingEnforcePermissionHelper",
+ briefDescription = """Missing permission-enforcing method call in AIDL method
+ |annotated with @EnforcePermission""".trimMargin(),
+ explanation = EXPLANATION,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ EnforcePermissionDetector::class.java,
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+ )
+ )
+
+ val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
+ id = "MisusingEnforcePermissionAnnotation",
+ briefDescription = "@EnforcePermission cannot be used here",
+ explanation = EXPLANATION,
+ category = Category.SECURITY,
+ priority = 6,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ EnforcePermissionDetector::class.java,
+ EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
+ )
+ )
+
+ /**
+ * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
+ * resulting in an incorrect Location if used directly
+ */
+ private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
+ if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
+ if (firstExpression is UDeclarationsExpression) {
+ return firstExpression.declarations.firstOrNull()?.sourcePsi
+ }
+ return null
+ }
}
}
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
deleted file mode 100644
index 758de4dfccf8..000000000000
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2022 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.google.android.lint.aidl
-
-import com.android.tools.lint.client.api.UElementHandler
-import com.android.tools.lint.detector.api.Category
-import com.android.tools.lint.detector.api.Detector
-import com.android.tools.lint.detector.api.Implementation
-import com.android.tools.lint.detector.api.Issue
-import com.android.tools.lint.detector.api.JavaContext
-import com.android.tools.lint.detector.api.Scope
-import com.android.tools.lint.detector.api.Severity
-import com.android.tools.lint.detector.api.SourceCodeScanner
-import com.google.android.lint.findCallExpression
-import com.intellij.psi.PsiElement
-import org.jetbrains.uast.UBlockExpression
-import org.jetbrains.uast.UDeclarationsExpression
-import org.jetbrains.uast.UElement
-import org.jetbrains.uast.UExpression
-import org.jetbrains.uast.UMethod
-import org.jetbrains.uast.skipParenthesizedExprDown
-
-import java.util.EnumSet
-
-class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
- override fun getApplicableUastTypes(): List<Class<out UElement?>> =
- listOf(UMethod::class.java)
-
- override fun createUastHandler(context: JavaContext): UElementHandler = AidlStubHandler(context)
-
- private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
- override fun visitMethod(node: UMethod) {
- if (context.evaluator.isAbstract(node)) return
- if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
-
- if (!isContainedInSubclassOfStub(context, node)) {
- context.report(
- ISSUE_MISUSING_ENFORCE_PERMISSION,
- node,
- context.getLocation(node),
- "The class of ${node.name} does not inherit from an AIDL generated Stub class"
- )
- return
- }
-
- val targetExpression = getHelperMethodCallSourceString(node)
- val message =
- "Method must start with $targetExpression or super.${node.name}(), if applicable"
-
- val firstExpression = (node.uastBody as? UBlockExpression)
- ?.expressions?.firstOrNull()
-
- if (firstExpression == null) {
- context.report(
- ISSUE_ENFORCE_PERMISSION_HELPER,
- context.getLocation(node),
- message,
- )
- return
- }
-
- val firstExpressionSource = firstExpression.skipParenthesizedExprDown()
- .asSourceString()
- .filterNot(Char::isWhitespace)
-
- if (firstExpressionSource != targetExpression &&
- firstExpressionSource != "super.$targetExpression") {
- // calling super.<methodName>() is also legal
- val directSuper = context.evaluator.getSuperMethod(node)
- val firstCall = findCallExpression(firstExpression)?.resolve()
- if (directSuper != null && firstCall == directSuper) return
-
- val locationTarget = getLocationTarget(firstExpression)
- val expressionLocation = context.getLocation(locationTarget)
-
- context.report(
- ISSUE_ENFORCE_PERMISSION_HELPER,
- context.getLocation(node),
- message,
- getHelperMethodFix(node, expressionLocation),
- )
- }
- }
- }
-
- companion object {
- private const val HELPER_SUFFIX = "_enforcePermission"
-
- private const val EXPLANATION = """
- The @EnforcePermission annotation can only be used on methods whose class extends from
- the Stub class generated by the AIDL compiler. When @EnforcePermission is applied, the
- AIDL compiler generates a Stub method to do the permission check called yourMethodName$HELPER_SUFFIX.
-
- yourMethodName$HELPER_SUFFIX must be executed before any other operation. To do that, you can
- either call it directly, or call it indirectly via super.yourMethodName().
- """
-
- val ISSUE_ENFORCE_PERMISSION_HELPER: Issue = Issue.create(
- id = "MissingEnforcePermissionHelper",
- briefDescription = """Missing permission-enforcing method call in AIDL method
- |annotated with @EnforcePermission""".trimMargin(),
- explanation = EXPLANATION,
- category = Category.SECURITY,
- priority = 6,
- severity = Severity.ERROR,
- implementation = Implementation(
- EnforcePermissionHelperDetector::class.java,
- EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
- )
- )
-
- val ISSUE_MISUSING_ENFORCE_PERMISSION: Issue = Issue.create(
- id = "MisusingEnforcePermissionAnnotation",
- briefDescription = "@EnforcePermission cannot be used here",
- explanation = EXPLANATION,
- category = Category.SECURITY,
- priority = 6,
- severity = Severity.ERROR,
- implementation = Implementation(
- EnforcePermissionHelperDetector::class.java,
- EnumSet.of(Scope.JAVA_FILE, Scope.TEST_SOURCES)
- )
- )
-
- /**
- * handles an edge case with UDeclarationsExpression, where sourcePsi is null,
- * resulting in an incorrect Location if used directly
- */
- private fun getLocationTarget(firstExpression: UExpression): PsiElement? {
- if (firstExpression.sourcePsi != null) return firstExpression.sourcePsi
- if (firstExpression is UDeclarationsExpression) {
- return firstExpression.declarations.firstOrNull()?.sourcePsi
- }
- return null
- }
- }
-}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
index 5a63bb4084d2..3ef02f865355 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
@@ -25,10 +25,10 @@ import com.android.tools.lint.detector.api.Issue
@Suppress("UnstableApiUsage")
class EnforcePermissionHelperDetectorCodegenTest : LintDetectorTest() {
- override fun getDetector(): Detector = EnforcePermissionHelperDetector()
+ override fun getDetector(): Detector = EnforcePermissionDetector()
override fun getIssues(): List<Issue> = listOf(
- EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER
+ EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER
)
override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
index 10a6e1da91dc..64e2bfbad7bb 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -20,10 +20,10 @@ import com.android.tools.lint.checks.infrastructure.LintDetectorTest
import com.android.tools.lint.checks.infrastructure.TestLintTask
class EnforcePermissionHelperDetectorTest : LintDetectorTest() {
- override fun getDetector() = EnforcePermissionHelperDetector()
+ override fun getDetector() = EnforcePermissionDetector()
override fun getIssues() = listOf(
- EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
- EnforcePermissionHelperDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
+ EnforcePermissionDetector.ISSUE_ENFORCE_PERMISSION_HELPER,
+ EnforcePermissionDetector.ISSUE_MISUSING_ENFORCE_PERMISSION
)
override fun lint(): TestLintTask = super.lint().allowMissingSdk()