diff options
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() |