diff --git a/res/layout/battery_chart_graph.xml b/res/layout/battery_chart_graph.xml index 9e816ed9ba1..b84f38a55c5 100644 --- a/res/layout/battery_chart_graph.xml +++ b/res/layout/battery_chart_graph.xml @@ -36,8 +36,7 @@ android:id="@+id/battery_chart_group" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" - android:alpha="0"> + android:orientation="vertical"> diff --git a/res/layout/display_topology_preference.xml b/res/layout/display_topology_preference.xml index d2e430027a5..beaf816e28f 100644 --- a/res/layout/display_topology_preference.xml +++ b/res/layout/display_topology_preference.xml @@ -16,8 +16,9 @@ @@ -27,14 +28,14 @@ android:layout_width="match_parent" android:src="@drawable/display_topology_background"/> + android:layout_height="match_parent"/> + android:paddingBottom="10dp" + android:paddingTop="10dp" /> diff --git a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java index 09e2d97e48b..3bda864a105 100644 --- a/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityHearingAidsFragment.java @@ -96,17 +96,6 @@ public class AccessibilityHearingAidsFragment extends AccessibilityShortcutPrefe return mFeatureName; } - @Override - protected ComponentName getTileComponentName() { - return AccessibilityShortcutController.ACCESSIBILITY_HEARING_AIDS_TILE_COMPONENT_NAME; - } - - @Override - protected CharSequence getTileTooltipContent(int type) { - // No tooltip to be shown - return null; - } - @Override protected boolean showGeneralCategory() { // Have static preference under dynamically created PreferenceCategory KEY_GENERAL_CATEGORY. diff --git a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java index 23c3fd4ebb6..2a22902a196 100644 --- a/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragment.java @@ -20,10 +20,8 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums; import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList; import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_GENERAL_CATEGORY; -import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_SAVED_QS_TOOLTIP_TYPE; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.ComponentName; @@ -44,7 +42,6 @@ import androidx.preference.PreferenceScreen; import com.android.internal.accessibility.common.ShortcutConstants; import com.android.settings.R; -import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType; import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment; import com.android.settings.dashboard.RestrictedDashboardFragment; @@ -60,16 +57,12 @@ import java.util.Set; public abstract class AccessibilityShortcutPreferenceFragment extends RestrictedDashboardFragment implements ShortcutPreference.OnClickCallback { private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; - protected static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow"; protected ShortcutPreference mShortcutPreference; protected Dialog mDialog; private AccessibilityManager.TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; private AccessibilitySettingsContentObserver mSettingsContentObserver; - private AccessibilityQuickSettingsTooltipWindow mTooltipWindow; - private boolean mNeedsQSTooltipReshow = false; - private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT; public AccessibilityShortcutPreferenceFragment(String restrictionKey) { super(restrictionKey); @@ -81,26 +74,10 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted /** Returns the accessibility feature name. */ protected abstract CharSequence getLabelName(); - /** Returns the accessibility tile component name. */ - protected abstract ComponentName getTileComponentName(); - - /** Returns the accessibility tile tooltip content. */ - protected abstract CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type); - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Restore the user shortcut type and tooltip. - if (savedInstanceState != null) { - if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) { - mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW); - } - if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_TYPE)) { - mNeedsQSTooltipType = savedInstanceState.getInt(KEY_SAVED_QS_TOOLTIP_TYPE); - } - } - final int resId = getPreferenceScreenResId(); if (resId <= 0) { final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen( @@ -141,21 +118,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted return super.onCreateView(inflater, container, savedInstanceState); } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - // Reshow tooltip when activity recreate, such as rotate device. - if (mNeedsQSTooltipReshow) { - view.post(() -> { - final Activity activity = getActivity(); - if (activity != null && !activity.isFinishing()) { - showQuickSettingsTooltipIfNeeded(); - } - }); - } - } - @Override public void onResume() { super.onResume(); @@ -177,16 +139,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted super.onPause(); } - @Override - public void onSaveInstanceState(Bundle outState) { - final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing(); - if (mNeedsQSTooltipReshow || isTooltipWindowShowing) { - outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true); - outState.putInt(KEY_SAVED_QS_TOOLTIP_TYPE, mNeedsQSTooltipType); - } - super.onSaveInstanceState(outState); - } - @Override public Dialog onCreateDialog(int dialogId) { switch (dialogId) { @@ -289,7 +241,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted */ private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) { dialog.dismiss(); - showQuickSettingsTooltipIfNeeded(); } @VisibleForTesting @@ -362,26 +313,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); } - /** - * Shows the quick settings tooltip if the quick settings feature is assigned. The tooltip only - * shows once. - * - * @param type The quick settings tooltip type - */ - protected void showQuickSettingsTooltipIfNeeded(@QuickSettingsTooltipType int type) { - mNeedsQSTooltipType = type; - showQuickSettingsTooltipIfNeeded(); - } - - /** - * @deprecated made obsolete by quick settings rollout. - * - * (TODO 367414968: finish removal.) - */ - @Deprecated - private void showQuickSettingsTooltipIfNeeded() { - } - /** * Returns the user preferred shortcut types or the default shortcut types if not set */ diff --git a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java index e49078bbd71..f729cc7a2cc 100644 --- a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java +++ b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java @@ -71,11 +71,6 @@ public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeature return view; } - @Override - protected void onPreferenceToggled(String preferenceKey, boolean enabled) { - // Do nothing. - } - @Override protected void onProcessArguments(Bundle arguments) { super.onProcessArguments(arguments); diff --git a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java index e28622501ed..b779f9d2d6a 100644 --- a/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleColorInversionPreferenceFragment.java @@ -107,10 +107,6 @@ public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePrefere if (enabled == isEnabled) { return; } - - if (enabled) { - showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE); - } logAccessibilityServiceEnabled(mComponentName, enabled); Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF); } diff --git a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java index 5b2df5aeb2f..fe51e69845b 100644 --- a/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleDaltonizerPreferenceFragment.java @@ -158,10 +158,6 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF if (enabled == isEnabled) { return; } - - if (enabled) { - showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE); - } logAccessibilityServiceEnabled(mComponentName, enabled); Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF); } diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index 65a1cd4b532..49f22bf5994 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -21,7 +21,6 @@ import static com.android.internal.accessibility.common.ShortcutConstants.UserSh import static com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums; import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList; -import android.app.Activity; import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.ComponentName; @@ -85,8 +84,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment public static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; protected static final String KEY_TOP_INTRO_PREFERENCE = "top_intro"; protected static final String KEY_HTML_DESCRIPTION_PREFERENCE = "html_description"; - protected static final String KEY_SAVED_QS_TOOLTIP_RESHOW = "qs_tooltip_reshow"; - protected static final String KEY_SAVED_QS_TOOLTIP_TYPE = "qs_tooltip_type"; protected static final String KEY_ANIMATED_IMAGE = "animated_image"; // For html description of accessibility service, must follow the rule, such as // , a11y settings will get the resources successfully. @@ -112,10 +109,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment private CharSequence mDescription; private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; private AccessibilitySettingsContentObserver mSettingsContentObserver; - - private AccessibilityQuickSettingsTooltipWindow mTooltipWindow; - private boolean mNeedsQSTooltipReshow = false; - private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT; private ImageView mImageGetterCacheView; protected final Html.ImageGetter mImageGetter = (String str) -> { if (str != null && str.startsWith(IMG_PREFIX)) { @@ -133,16 +126,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment super.onCreate(savedInstanceState); onProcessArguments(getArguments()); - // Restore the user shortcut type and tooltip. - if (savedInstanceState != null) { - if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_RESHOW)) { - mNeedsQSTooltipReshow = savedInstanceState.getBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW); - } - if (savedInstanceState.containsKey(KEY_SAVED_QS_TOOLTIP_TYPE)) { - mNeedsQSTooltipType = savedInstanceState.getInt(KEY_SAVED_QS_TOOLTIP_TYPE); - } - } - final int resId = getPreferenceScreenResId(); if (resId <= 0) { final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen( @@ -227,16 +210,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment final SettingsMainSwitchBar switchBar = settingsActivity.getSwitchBar(); switchBar.hide(); - // Reshow tooltip when activity recreate, such as rotate device. - if (mNeedsQSTooltipReshow) { - view.post(() -> { - final Activity activity = getActivity(); - if (activity != null && !activity.isFinishing()) { - showQuickSettingsTooltipIfNeeded(); - } - }); - } - writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(getContext()); } @@ -261,24 +234,10 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment super.onPause(); } - @Override - public void onSaveInstanceState(Bundle outState) { - final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing(); - if (mNeedsQSTooltipReshow || isTooltipWindowShowing) { - outState.putBoolean(KEY_SAVED_QS_TOOLTIP_RESHOW, /* value= */ true); - outState.putInt(KEY_SAVED_QS_TOOLTIP_TYPE, mNeedsQSTooltipType); - } - super.onSaveInstanceState(outState); - } - @Override public void onDestroyView() { super.onDestroyView(); removeActionBarToggleSwitch(); - final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing(); - if (isTooltipWindowShowing) { - mTooltipWindow.dismiss(); - } } @Override @@ -314,7 +273,12 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment /** Returns the accessibility tile component name. */ abstract ComponentName getTileComponentName(); - /** Returns the accessibility tile tooltip content. */ + /** Returns the accessibility tile component name. + * + * @deprecated unused, as this class no longer displays tile tooltips. + * + * (TODO 367414968: finish removal.)*/ + @Deprecated abstract CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type); protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) { @@ -332,9 +296,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment } protected void onPreferenceToggled(String preferenceKey, boolean enabled) { - if (enabled) { - showQuickSettingsTooltipIfNeeded(); - } } protected void onInstallSwitchPreferenceToggleSwitch() { @@ -646,7 +607,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment */ private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) { dialog.dismiss(); - showQuickSettingsTooltipIfNeeded(); } protected void updateShortcutPreferenceData() { @@ -737,26 +697,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment } } - /** - * Shows the quick settings tooltip if the quick settings feature is assigned. The tooltip only - * shows once. - * - * @param type The quick settings tooltip type - */ - protected void showQuickSettingsTooltipIfNeeded(@QuickSettingsTooltipType int type) { - mNeedsQSTooltipType = type; - showQuickSettingsTooltipIfNeeded(); - } - - /** - * @deprecated made obsolete by quick settings rollout. - * - * (TODO 367414968: finish removal.) - */ - @Deprecated - private void showQuickSettingsTooltipIfNeeded() { - } - /** Returns user visible name of the tile by given {@link ComponentName}. */ protected CharSequence loadTileLabel(Context context, ComponentName componentName) { final PackageManager packageManager = context.getPackageManager(); diff --git a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java index 16911f6abb8..ff14021108d 100644 --- a/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleReduceBrightColorsPreferenceFragment.java @@ -149,9 +149,6 @@ public class ToggleReduceBrightColorsPreferenceFragment extends ToggleFeaturePre @Override protected void onPreferenceToggled(String preferenceKey, boolean enabled) { - if (enabled) { - showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE); - } logAccessibilityServiceEnabled(mComponentName, enabled); mColorDisplayManager.setReduceBrightColorsActivated(enabled); } diff --git a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt index 76abc031055..9cac7727a7f 100644 --- a/src/com/android/settings/connecteddevice/display/DisplayTopology.kt +++ b/src/com/android/settings/connecteddevice/display/DisplayTopology.kt @@ -16,14 +16,32 @@ package com.android.settings.connecteddevice.display +import android.app.WallpaperManager import com.android.settings.R import android.content.Context +import android.graphics.Color import android.graphics.Point import android.graphics.PointF import android.graphics.RectF +import android.graphics.drawable.Drawable +import android.graphics.drawable.ColorDrawable +import android.hardware.display.DisplayManager +import android.hardware.display.DisplayTopology +import android.hardware.display.DisplayTopology.TreeNode.POSITION_BOTTOM +import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT +import android.hardware.display.DisplayTopology.TreeNode.POSITION_RIGHT +import android.hardware.display.DisplayTopology.TreeNode.POSITION_TOP +import android.util.Log +import android.view.ViewGroup +import android.view.ViewTreeObserver +import android.widget.Button +import android.widget.FrameLayout +import android.widget.TextView +import androidx.annotation.VisibleForTesting import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder import java.util.Locale @@ -143,11 +161,27 @@ class TopologyScale( const val PREFERENCE_KEY = "display_topology_preference" +/** dp of padding on each side of a display block. */ +const val BLOCK_PADDING = 2 + /** * DisplayTopologyPreference allows the user to change the display topology * when there is one or more extended display attached. */ -class DisplayTopologyPreference(context : Context) : Preference(context) { +class DisplayTopologyPreference(context : Context) + : Preference(context), ViewTreeObserver.OnGlobalLayoutListener { + @VisibleForTesting lateinit var mPaneContent : FrameLayout + @VisibleForTesting lateinit var mPaneHolder : FrameLayout + @VisibleForTesting lateinit var mTopologyHint : TextView + + @VisibleForTesting var injector : Injector + + /** + * This is needed to prevent a repopulation of the pane causing another + * relayout and vice-versa ad infinitum. + */ + private var mPaneNeedsRefresh = false + init { layoutResource = R.layout.display_topology_preference @@ -155,5 +189,108 @@ class DisplayTopologyPreference(context : Context) : Preference(context) { isSelectable = false key = PREFERENCE_KEY + + injector = Injector() + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + + val newPane = holder.findViewById(R.id.display_topology_pane_content) as FrameLayout + if (this::mPaneContent.isInitialized) { + if (newPane == mPaneContent) { + return + } + mPaneContent.viewTreeObserver.removeOnGlobalLayoutListener(this) + } + mPaneContent = newPane + mPaneHolder = holder.itemView as FrameLayout + mTopologyHint = holder.findViewById(R.id.topology_hint) as TextView + mPaneContent.viewTreeObserver.addOnGlobalLayoutListener(this) + } + + override fun onAttached() { + // We don't know if topology changes happened when we were detached, as it is impossible to + // listen at that time (we must remove listeners when detaching). Setting this flag makes + // the following onGlobalLayout call refresh the pane. + mPaneNeedsRefresh = true + } + + override fun onGlobalLayout() { + if (mPaneNeedsRefresh) { + mPaneNeedsRefresh = false + refreshPane() + } + } + + open class Injector { + open fun displayTopology(context : Context) : DisplayTopology? { + val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager + return displayManager.displayTopology + } + + open fun wallpaper(context : Context) : Drawable { + return WallpaperManager.getInstance(context).drawable ?: ColorDrawable(Color.BLACK) + } + } + + private fun calcAbsRects( + dest : MutableMap, n : DisplayTopology.TreeNode, x : Float, y : Float) { + dest.put(n.displayId, RectF(x, y, x + n.width, y + n.height)) + + for (c in n.children) { + val (xoff, yoff) = when (c.position) { + POSITION_LEFT -> Pair(-c.width, +c.offset) + POSITION_RIGHT -> Pair(+n.width, +c.offset) + POSITION_TOP -> Pair(+c.offset, -c.height) + POSITION_BOTTOM -> Pair(+c.offset, +n.height) + else -> throw IllegalStateException("invalid position for display: ${c}") + } + calcAbsRects(dest, c, x + xoff, y + yoff) + } + } + + private fun refreshPane() { + mPaneContent.removeAllViews() + + val root = injector.displayTopology(context)?.root + if (root == null) { + // This occurs when no topology is active. + // TODO(b/352648432): show main display or mirrored displays rather than an empty pane. + mTopologyHint.text = "" + return + } + mTopologyHint.text = context.getString(R.string.external_display_topology_hint) + + val blocksPos = buildMap { calcAbsRects(this, root, x = 0f, y = 0f) } + + val scaling = TopologyScale( + mPaneContent.width, minEdgeLength = 60, maxBlockRatio = 0.12f, blocksPos.values) + mPaneHolder.layoutParams.let { + if (it.height != scaling.paneHeight) { + it.height = scaling.paneHeight + mPaneHolder.layoutParams = it + } + } + val wallpaper = injector.wallpaper(context) + blocksPos.values.forEach { p -> + Button(context).apply { + isScrollContainer = false + isVerticalScrollBarEnabled = false + isHorizontalScrollBarEnabled = false + background = wallpaper + val topLeft = scaling.displayToPaneCoor(PointF(p.left, p.top)) + val bottomRight = scaling.displayToPaneCoor(PointF(p.right, p.bottom)) + + mPaneContent.addView(this) + + val layout = layoutParams + layout.width = bottomRight.x - topLeft.x - BLOCK_PADDING * 2 + layout.height = bottomRight.y - topLeft.y - BLOCK_PADDING * 2 + layoutParams = layout + x = (topLeft.x + BLOCK_PADDING).toFloat() + y = (topLeft.y + BLOCK_PADDING).toFloat() + } + } } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index 5e17f4b4871..2edbf99f55a 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -274,7 +274,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll } if (mDailyChartView != dailyChartView || mHourlyChartView != hourlyChartView) { mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView)); - animateBatteryChartViewGroup(); } if (mBatteryChartViewGroup != null) { final View grandparentView = (View) mBatteryChartViewGroup.getParent(); diff --git a/src/com/android/settings/gestures/OneHandedSettings.java b/src/com/android/settings/gestures/OneHandedSettings.java index 03788889e8b..0a2599e4650 100644 --- a/src/com/android/settings/gestures/OneHandedSettings.java +++ b/src/com/android/settings/gestures/OneHandedSettings.java @@ -22,9 +22,9 @@ import android.content.ComponentName; import android.content.Context; import android.os.Bundle; import android.os.UserHandle; -import android.util.Log; import android.view.LayoutInflater; import android.view.ViewGroup; +import android.widget.CompoundButton; import androidx.recyclerview.widget.RecyclerView; @@ -33,7 +33,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; import com.android.settings.accessibility.AccessibilityFragmentUtils; import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment; -import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexableRaw; @@ -82,12 +81,7 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { final MainSwitchPreference mainSwitchPreference = getPreferenceScreen().findPreference(ONE_HANDED_MAIN_SWITCH_KEY); - mainSwitchPreference.addOnSwitchChangeListener((switchView, isChecked) -> { - switchView.setChecked(isChecked); - if (isChecked) { - showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE); - } - }); + mainSwitchPreference.addOnSwitchChangeListener(CompoundButton::setChecked); } @Override @@ -145,24 +139,6 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment { return mFeatureName; } - @Override - protected ComponentName getTileComponentName() { - return AccessibilityShortcutController.ONE_HANDED_TILE_COMPONENT_NAME; - } - - @Override - protected CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) { - final Context context = getContext(); - if (context == null) { - Log.w(TAG, "OneHandedSettings not attached to a context."); - return null; - } - return type == QuickSettingsTooltipType.GUIDE_TO_EDIT - ? context.getText(R.string.accessibility_one_handed_mode_qs_tooltip_content) - : context.getText( - R.string.accessibility_one_handed_mode_auto_added_qs_tooltip_content); - } - @Override protected int getPreferenceScreenResId() { return R.xml.one_handed_settings; diff --git a/src/com/android/settings/network/telephony/SubscriptionRepository.kt b/src/com/android/settings/network/telephony/SubscriptionRepository.kt index 43bba0759a2..33c1e655040 100644 --- a/src/com/android/settings/network/telephony/SubscriptionRepository.kt +++ b/src/com/android/settings/network/telephony/SubscriptionRepository.kt @@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn +import java.util.stream.Collectors private const val TAG = "SubscriptionRepository" @@ -154,6 +155,18 @@ class SubscriptionRepository(private val context: Context) { .conflate() .flowOn(Dispatchers.Default) + fun removableSubscriptionInfoListFlow(): Flow> { + return subscriptionsChangedFlow() + .map { + subscriptionManager.availableSubscriptionInfoList?.stream() + ?.filter { sub: SubscriptionInfo -> !sub.isEmbedded } + ?.collect(Collectors.toList()) ?: emptyList() + } + .conflate() + .onEach { Log.d(TAG, "getRemovableSubscriptionVisibleFlow: $it") } + .flowOn(Dispatchers.Default) + } + @OptIn(ExperimentalCoroutinesApi::class) fun phoneNumberFlow(subId: Int): Flow = activeSubscriptionInfoFlow(subId).flatMapLatest { subInfo -> diff --git a/src/com/android/settings/service/PreferenceService.kt b/src/com/android/settings/service/PreferenceService.kt index d07e78d333e..81ab5840a04 100644 --- a/src/com/android/settings/service/PreferenceService.kt +++ b/src/com/android/settings/service/PreferenceService.kt @@ -19,7 +19,6 @@ package com.android.settings.service import android.app.Application import android.os.Binder import android.os.OutcomeReceiver -import android.os.Process import android.service.settings.preferences.GetValueRequest import android.service.settings.preferences.GetValueResult import android.service.settings.preferences.MetadataRequest @@ -37,7 +36,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch -import java.lang.Exception class PreferenceService : SettingsPreferenceService() { @@ -49,14 +47,22 @@ class PreferenceService : SettingsPreferenceService() { override fun onGetAllPreferenceMetadata( request: MetadataRequest, - callback: OutcomeReceiver + callback: OutcomeReceiver, ) { + // MUST get pid/uid in binder thread + val callingPid = Binder.getCallingPid() + val callingUid = Binder.getCallingUid() scope.launch { - val graphProto = graphApi.invoke(application, Process.myUid(), Binder.getCallingUid(), - GetPreferenceGraphRequest( - includeValue = false, - flags = PreferenceGetterFlags.METADATA - )) + val graphProto = + graphApi.invoke( + application, + callingPid, + callingUid, + GetPreferenceGraphRequest( + includeValue = false, + flags = PreferenceGetterFlags.METADATA, + ), + ) val result = transformCatalystGetMetadataResponse(this@PreferenceService, graphProto) callback.onResult(result) } @@ -64,17 +70,16 @@ class PreferenceService : SettingsPreferenceService() { override fun onGetPreferenceValue( request: GetValueRequest, - callback: OutcomeReceiver + callback: OutcomeReceiver, ) { + // MUST get pid/uid in binder thread + val callingPid = Binder.getCallingPid() + val callingUid = Binder.getCallingUid() scope.launch { val apiRequest = transformFrameworkGetValueRequest(request) - val response = getApiHandler.invoke(application, Process.myUid(), - Binder.getCallingUid(), apiRequest) - val result = transformCatalystGetValueResponse( - this@PreferenceService, - request, - response - ) + val response = getApiHandler.invoke(application, callingPid, callingUid, apiRequest) + val result = + transformCatalystGetValueResponse(this@PreferenceService, request, response) if (result == null) { callback.onError(IllegalStateException("No response")) } else { @@ -85,8 +90,11 @@ class PreferenceService : SettingsPreferenceService() { override fun onSetPreferenceValue( request: SetValueRequest, - callback: OutcomeReceiver + callback: OutcomeReceiver, ) { + // MUST get pid/uid in binder thread + val callingPid = Binder.getCallingPid() + val callingUid = Binder.getCallingUid() scope.launch { val apiRequest = transformFrameworkSetValueRequest(request) if (apiRequest == null) { @@ -94,8 +102,7 @@ class PreferenceService : SettingsPreferenceService() { SetValueResult.Builder(SetValueResult.RESULT_INVALID_REQUEST).build() ) } else { - val response = setApiHandler.invoke(application, Process.myUid(), - Binder.getCallingUid(), apiRequest) + val response = setApiHandler.invoke(application, callingPid, callingUid, apiRequest) callback.onResult(transformCatalystSetValueResponse(response)) } @@ -106,9 +113,9 @@ class PreferenceService : SettingsPreferenceService() { private class GraphProvider(override val id: Int) : GetPreferenceGraphApiHandler(emptySet()) { override fun hasPermission( application: Application, - myUid: Int, + callingPid: Int, callingUid: Int, - request: GetPreferenceGraphRequest + request: GetPreferenceGraphRequest, ) = true } } diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java deleted file mode 100644 index f808924fa41..00000000000 --- a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.settings.sim.receivers; - -import static android.content.Context.MODE_PRIVATE; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Looper; -import android.os.UserHandle; -import android.provider.Settings; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; -import android.telephony.UiccCardInfo; -import android.telephony.UiccPortInfo; -import android.telephony.UiccSlotInfo; -import android.util.Log; - -import com.android.settings.flags.Flags; -import com.android.settings.network.SubscriptionUtil; -import com.android.settings.network.UiccSlotUtil; -import com.android.settings.network.UiccSlotsException; -import com.android.settings.sim.ChooseSimActivity; -import com.android.settings.sim.DsdsDialogActivity; -import com.android.settings.sim.SimActivationNotifier; -import com.android.settings.sim.SimNotificationService; -import com.android.settings.sim.SwitchToEsimConfirmDialogActivity; - -import com.google.common.collect.ImmutableList; - -import java.util.List; -import java.util.stream.Collectors; - -import javax.annotation.Nullable; - -/** Perform actions after a slot change event is triggered. */ -public class SimSlotChangeHandler { - private static final String TAG = "SimSlotChangeHandler"; - - private static final String EUICC_PREFS = "euicc_prefs"; - // Shared preference keys - private static final String KEY_REMOVABLE_SLOT_STATE = "removable_slot_state"; - private static final String KEY_SUW_PSIM_ACTION = "suw_psim_action"; - // User's last removable SIM insertion / removal action during SUW. - private static final int LAST_USER_ACTION_IN_SUW_NONE = 0; - private static final int LAST_USER_ACTION_IN_SUW_INSERT = 1; - private static final int LAST_USER_ACTION_IN_SUW_REMOVE = 2; - - private static volatile SimSlotChangeHandler sSlotChangeHandler; - - /** Returns a SIM slot change handler singleton. */ - public static SimSlotChangeHandler get() { - if (sSlotChangeHandler == null) { - synchronized (SimSlotChangeHandler.class) { - if (sSlotChangeHandler == null) { - sSlotChangeHandler = new SimSlotChangeHandler(); - } - } - } - return sSlotChangeHandler; - } - - private SubscriptionManager mSubMgr; - private TelephonyManager mTelMgr; - private Context mContext; - - void onSlotsStatusChange(Context context) { - init(context); - - if (Looper.myLooper() == Looper.getMainLooper()) { - throw new IllegalStateException("Cannot be called from main thread."); - } - - UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo(); - if (removableSlotInfo == null) { - Log.e(TAG, "Unable to find the removable slot. Do nothing."); - return; - } - Log.i(TAG, "The removableSlotInfo: " + removableSlotInfo); - int lastRemovableSlotState = getLastRemovableSimSlotState(mContext); - int currentRemovableSlotState = removableSlotInfo.getCardStateInfo(); - Log.d(TAG, - "lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: " - + currentRemovableSlotState); - boolean isRemovableSimInserted = - lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT - && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT; - boolean isRemovableSimRemoved = - lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT - && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT; - - // Sets the current removable slot state. - setRemovableSimSlotState(mContext, currentRemovableSlotState); - - if (mTelMgr.getActiveModemCount() > 1) { - if (!isRemovableSimInserted) { - Log.d(TAG, "Removable Sim is not inserted in DSDS mode. Do nothing."); - return; - } - - if (Flags.isDualSimOnboardingEnabled()) { - // ForNewUi, when the user inserts the psim, showing the sim onboarding for the user - // to setup the sim switching or the default data subscription in DSDS. - // Will show dialog for below case. - // 1. the psim slot is not active. - // 2. there are one or more active sim. - handleRemovableSimInsertWhenDsds(removableSlotInfo); - return; - } else if (!isMultipleEnabledProfilesSupported()) { - Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing."); - return; - } else if (isMultipleEnabledProfilesSupported()) { - handleRemovableSimInsertUnderDsdsMep(removableSlotInfo); - return; - } - } - - if (isRemovableSimInserted) { - handleSimInsert(removableSlotInfo); - return; - } - if (isRemovableSimRemoved) { - handleSimRemove(removableSlotInfo); - return; - } - Log.i(TAG, "Do nothing on slot status changes."); - } - - void onSuwFinish(Context context) { - init(context); - - if (Looper.myLooper() == Looper.getMainLooper()) { - throw new IllegalStateException("Cannot be called from main thread."); - } - - if (mTelMgr.getActiveModemCount() > 1) { - Log.i(TAG, "The device is already in DSDS mode. Do nothing."); - return; - } - - UiccSlotInfo removableSlotInfo = getRemovableUiccSlotInfo(); - if (removableSlotInfo == null) { - Log.e(TAG, "Unable to find the removable slot. Do nothing."); - return; - } - - boolean embeddedSimExist = getGroupedEmbeddedSubscriptions().size() != 0; - int removableSlotAction = getSuwRemovableSlotAction(mContext); - setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_NONE); - - if (embeddedSimExist - && removableSlotInfo.getCardStateInfo() == UiccSlotInfo.CARD_STATE_INFO_PRESENT) { - if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) { - Log.i(TAG, "DSDS condition satisfied. Show notification."); - SimNotificationService.scheduleSimNotification( - mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS); - } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) { - Log.i( - TAG, - "Both removable SIM and eSIM are present. DSDS condition doesn't" - + " satisfied. User inserted pSIM during SUW. Show choose SIM" - + " screen."); - startChooseSimActivity(true); - } - } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) { - handleSimRemove(removableSlotInfo); - } - } - - private void init(Context context) { - mSubMgr = - (SubscriptionManager) - context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); - mTelMgr = context.getSystemService(TelephonyManager.class); - mContext = context; - } - - private void handleSimInsert(UiccSlotInfo removableSlotInfo) { - Log.i(TAG, "Handle SIM inserted."); - if (!isSuwFinished(mContext)) { - Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished"); - setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_INSERT); - return; - } - if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) { - Log.i(TAG, "The removable slot is already active. Do nothing."); - return; - } - - if (hasActiveEsimSubscription()) { - if (mTelMgr.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) { - Log.i(TAG, "Enabled profile exists. DSDS condition satisfied."); - if (Flags.isDualSimOnboardingEnabled()) { - // enable dsds by sim onboarding flow - handleRemovableSimInsertWhenDsds(removableSlotInfo); - } else { - startDsdsDialogActivity(); - } - } else { - Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied."); - startChooseSimActivity(true); - } - return; - } - - Log.i( - TAG, - "No enabled eSIM profile. Ready to switch to removable slot and show" - + " notification."); - try { - UiccSlotUtil.switchToRemovableSlot( - UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext.getApplicationContext()); - } catch (UiccSlotsException e) { - Log.e(TAG, "Failed to switch to removable slot."); - return; - } - SimNotificationService.scheduleSimNotification( - mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT); - } - - private void handleSimRemove(UiccSlotInfo removableSlotInfo) { - Log.i(TAG, "Handle SIM removed."); - - if (!isSuwFinished(mContext)) { - Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished"); - setSuwRemovableSlotAction(mContext, LAST_USER_ACTION_IN_SUW_REMOVE); - return; - } - - List groupedEmbeddedSubscriptions = getGroupedEmbeddedSubscriptions(); - if (groupedEmbeddedSubscriptions.size() == 0 || !removableSlotInfo.getPorts().stream() - .findFirst().get().isActive()) { - Log.i(TAG, "eSIM slot is active or no subscriptions exist. Do nothing." - + " The removableSlotInfo: " + removableSlotInfo - + ", groupedEmbeddedSubscriptions: " + groupedEmbeddedSubscriptions); - return; - } - - // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that - // profile. - if (groupedEmbeddedSubscriptions.size() == 1) { - Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch."); - startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions.get(0)); - return; - } - - // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose - // the number they want to use. - Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use."); - startChooseSimActivity(false); - } - - private boolean hasOtherActiveSubInfo(int psimSubId) { - List activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr); - return activeSubs.stream() - .anyMatch(subscriptionInfo -> subscriptionInfo.getSubscriptionId() != psimSubId); - } - - private boolean hasAnyPortActiveInSlot(UiccSlotInfo removableSlotInfo) { - return removableSlotInfo.getPorts().stream().anyMatch(UiccPortInfo::isActive); - } - - private void handleRemovableSimInsertWhenDsds(UiccSlotInfo removableSlotInfo) { - Log.i(TAG, "ForNewUi: Handle Removable SIM inserted"); - List subscriptionInfos = getAvailableRemovableSubscription(); - if (subscriptionInfos.isEmpty()) { - Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing."); - return; - } - Log.d(TAG, "getAvailableRemovableSubscription:" + subscriptionInfos); - int psimSubId = subscriptionInfos.get(0).getSubscriptionId(); - if (!hasAnyPortActiveInSlot(removableSlotInfo) || hasOtherActiveSubInfo(psimSubId)) { - Log.d(TAG, "ForNewUi Start Setup flow"); - startSimConfirmDialogActivity(psimSubId); - } - } - - private void handleRemovableSimInsertUnderDsdsMep(UiccSlotInfo removableSlotInfo) { - Log.i(TAG, "Handle Removable SIM inserted under DSDS+Mep."); - - if (removableSlotInfo.getPorts().stream().findFirst().get().isActive()) { - Log.i(TAG, "The removable slot is already active. Do nothing. removableSlotInfo: " - + removableSlotInfo); - return; - } - - List subscriptionInfos = getAvailableRemovableSubscription(); - if (subscriptionInfos.isEmpty()) { - Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing."); - return; - } - Log.d(TAG, "getAvailableRemovableSubscription:" + subscriptionInfos); - startSimConfirmDialogActivity(subscriptionInfos.get(0).getSubscriptionId()); - } - - private int getLastRemovableSimSlotState(Context context) { - final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); - return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT); - } - - private void setRemovableSimSlotState(Context context, int state) { - final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); - prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply(); - Log.d(TAG, "setRemovableSimSlotState: " + state); - } - - private int getSuwRemovableSlotAction(Context context) { - final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); - return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE); - } - - private void setSuwRemovableSlotAction(Context context, int action) { - final SharedPreferences prefs = context.getSharedPreferences(EUICC_PREFS, MODE_PRIVATE); - prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply(); - } - - @Nullable - private UiccSlotInfo getRemovableUiccSlotInfo() { - UiccSlotInfo[] slotInfos = mTelMgr.getUiccSlotsInfo(); - if (slotInfos == null) { - Log.e(TAG, "slotInfos is null. Unable to get slot infos."); - return null; - } - for (UiccSlotInfo slotInfo : slotInfos) { - if (slotInfo != null && slotInfo.isRemovable()) { - return slotInfo; - } - } - return null; - } - - private static boolean isSuwFinished(Context context) { - try { - // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed. - return Settings.Global.getInt( - context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED) - == 1; - } catch (Settings.SettingNotFoundException e) { - Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e); - return false; - } - } - - private boolean hasActiveEsimSubscription() { - List activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr); - return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded); - } - - private List getGroupedEmbeddedSubscriptions() { - List groupedSubscriptions = - SubscriptionUtil.getSelectableSubscriptionInfoList(mContext); - if (groupedSubscriptions == null) { - return ImmutableList.of(); - } - return ImmutableList.copyOf( - groupedSubscriptions.stream() - .filter(sub -> sub.isEmbedded()) - .collect(Collectors.toList())); - } - - protected List getAvailableRemovableSubscription() { - List removableSubscriptions = - SubscriptionUtil.getAvailableSubscriptions(mContext); - return ImmutableList.copyOf( - removableSubscriptions.stream() - // ToDo: This condition is for psim only. If device supports removable - // esim, it needs an new condition. - .filter(sub -> !sub.isEmbedded()) - .collect(Collectors.toList())); - } - - private void startChooseSimActivity(boolean psimInserted) { - Intent intent = ChooseSimActivity.getIntent(mContext); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted); - mContext.startActivityAsUser(intent, UserHandle.SYSTEM); - } - - private void startSwitchSlotConfirmDialogActivity(SubscriptionInfo subscriptionInfo) { - Intent intent = new Intent(mContext, SwitchToEsimConfirmDialogActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo); - mContext.startActivityAsUser(intent, UserHandle.SYSTEM); - } - - private void startDsdsDialogActivity() { - Intent intent = new Intent(mContext, DsdsDialogActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mContext.startActivityAsUser(intent, UserHandle.SYSTEM); - } - - private void startSimConfirmDialogActivity(int subId) { - if (!isSuwFinished(mContext)) { - Log.d(TAG, "Still in SUW. Do nothing"); - return; - } - if (!SubscriptionManager.isUsableSubscriptionId(subId)) { - Log.i(TAG, "Unable to enable subscription due to invalid subscription ID."); - return; - } - Log.d(TAG, "Start ToggleSubscriptionDialogActivity with " + subId + " under DSDS+Mep."); - SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, true, true); - } - - private boolean isMultipleEnabledProfilesSupported() { - List cardInfos = mTelMgr.getUiccCardsInfo(); - if (cardInfos == null) { - Log.d(TAG, "UICC cards info list is empty."); - return false; - } - return cardInfos.stream().anyMatch( - cardInfo -> cardInfo.isMultipleEnabledProfilesSupported()); - } - - private SimSlotChangeHandler() {} -} diff --git a/src/com/android/settings/sim/receivers/SimSlotChangeHandler.kt b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.kt new file mode 100644 index 00000000000..940184a926a --- /dev/null +++ b/src/com/android/settings/sim/receivers/SimSlotChangeHandler.kt @@ -0,0 +1,480 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.sim.receivers + +import android.content.Context +import android.content.Intent +import android.os.Looper +import android.os.UserHandle +import android.provider.Settings +import android.provider.Settings.SettingNotFoundException +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.telephony.UiccCardInfo +import android.telephony.UiccSlotInfo +import android.util.Log +import com.android.settings.flags.Flags +import com.android.settings.network.SubscriptionUtil +import com.android.settings.network.UiccSlotUtil +import com.android.settings.network.UiccSlotsException +import com.android.settings.network.telephony.SubscriptionRepository +import com.android.settings.sim.ChooseSimActivity +import com.android.settings.sim.DsdsDialogActivity +import com.android.settings.sim.SimActivationNotifier +import com.android.settings.sim.SimNotificationService +import com.android.settings.sim.SwitchToEsimConfirmDialogActivity +import com.google.common.collect.ImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull +import java.util.stream.Collectors +import kotlin.concurrent.Volatile + +/** Perform actions after a slot change event is triggered. */ +class SimSlotChangeHandler private constructor() { + private var mSubMgr: SubscriptionManager? = null + private var mTelMgr: TelephonyManager? = null + private var mContext: Context? = null + + fun onSlotsStatusChange(context: Context) { + init(context) + + check(Looper.myLooper() != Looper.getMainLooper()) { "Cannot be called from main thread." } + + val removableSlotInfo = removableUiccSlotInfo + if (removableSlotInfo == null) { + Log.e(TAG, "Unable to find the removable slot. Do nothing.") + return + } + Log.i( + TAG, + "The removableSlotInfo: $removableSlotInfo" + ) + val lastRemovableSlotState = getLastRemovableSimSlotState(mContext!!) + val currentRemovableSlotState = removableSlotInfo.cardStateInfo + Log.d( + TAG, + ("lastRemovableSlotState: " + lastRemovableSlotState + ",currentRemovableSlotState: " + + currentRemovableSlotState) + ) + val isRemovableSimInserted = + lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT + && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT + val isRemovableSimRemoved = + lastRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_PRESENT + && currentRemovableSlotState == UiccSlotInfo.CARD_STATE_INFO_ABSENT + + // Sets the current removable slot state. + setRemovableSimSlotState(mContext!!, currentRemovableSlotState) + + if (mTelMgr!!.activeModemCount > 1) { + if (!isRemovableSimInserted) { + Log.d(TAG, "Removable Sim is not inserted in DSDS mode. Do nothing.") + return + } + + if (Flags.isDualSimOnboardingEnabled()) { + // ForNewUi, when the user inserts the psim, showing the sim onboarding for the user + // to setup the sim switching or the default data subscription in DSDS. + // Will show dialog for below case. + // 1. the psim slot is not active. + // 2. there are one or more active sim. + handleRemovableSimInsertWhenDsds(removableSlotInfo) + return + } else if (!isMultipleEnabledProfilesSupported) { + Log.d(TAG, "The device is already in DSDS mode and no MEP. Do nothing.") + return + } else if (isMultipleEnabledProfilesSupported) { + handleRemovableSimInsertUnderDsdsMep(removableSlotInfo) + return + } + } + + if (isRemovableSimInserted) { + handleSimInsert(removableSlotInfo) + return + } + if (isRemovableSimRemoved) { + handleSimRemove(removableSlotInfo) + return + } + Log.i(TAG, "Do nothing on slot status changes.") + } + + fun onSuwFinish(context: Context) { + init(context) + + check(Looper.myLooper() != Looper.getMainLooper()) { "Cannot be called from main thread." } + + if (mTelMgr!!.activeModemCount > 1) { + Log.i(TAG, "The device is already in DSDS mode. Do nothing.") + return + } + + val removableSlotInfo = removableUiccSlotInfo + if (removableSlotInfo == null) { + Log.e(TAG, "Unable to find the removable slot. Do nothing.") + return + } + + val embeddedSimExist = groupedEmbeddedSubscriptions.size != 0 + val removableSlotAction = getSuwRemovableSlotAction(mContext!!) + setSuwRemovableSlotAction(mContext!!, LAST_USER_ACTION_IN_SUW_NONE) + + if (embeddedSimExist + && removableSlotInfo.cardStateInfo == UiccSlotInfo.CARD_STATE_INFO_PRESENT + ) { + if (mTelMgr!!.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) { + Log.i(TAG, "DSDS condition satisfied. Show notification.") + SimNotificationService.scheduleSimNotification( + mContext, SimActivationNotifier.NotificationType.ENABLE_DSDS + ) + } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_INSERT) { + Log.i( + TAG, + ("Both removable SIM and eSIM are present. DSDS condition doesn't" + + " satisfied. User inserted pSIM during SUW. Show choose SIM" + + " screen.") + ) + startChooseSimActivity(true) + } + } else if (removableSlotAction == LAST_USER_ACTION_IN_SUW_REMOVE) { + handleSimRemove(removableSlotInfo) + } + } + + private fun init(context: Context) { + mSubMgr = + context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager + mTelMgr = context.getSystemService(TelephonyManager::class.java) + mContext = context + } + + private fun handleSimInsert(removableSlotInfo: UiccSlotInfo) { + Log.i(TAG, "Handle SIM inserted.") + if (!isSuwFinished(mContext!!)) { + Log.i(TAG, "Still in SUW. Handle SIM insertion after SUW is finished") + setSuwRemovableSlotAction(mContext!!, LAST_USER_ACTION_IN_SUW_INSERT) + return + } + if (removableSlotInfo.ports.stream().findFirst().get().isActive) { + Log.i(TAG, "The removable slot is already active. Do nothing.") + return + } + + if (hasActiveEsimSubscription()) { + if (mTelMgr!!.isMultiSimSupported() == TelephonyManager.MULTISIM_ALLOWED) { + Log.i(TAG, "Enabled profile exists. DSDS condition satisfied.") + if (Flags.isDualSimOnboardingEnabled()) { + // enable dsds by sim onboarding flow + handleRemovableSimInsertWhenDsds(removableSlotInfo) + } else { + startDsdsDialogActivity() + } + } else { + Log.i(TAG, "Enabled profile exists. DSDS condition not satisfied.") + startChooseSimActivity(true) + } + return + } + + Log.i( + TAG, + "No enabled eSIM profile. Ready to switch to removable slot and show" + + " notification." + ) + try { + UiccSlotUtil.switchToRemovableSlot( + UiccSlotUtil.INVALID_PHYSICAL_SLOT_ID, mContext!!.applicationContext + ) + } catch (e: UiccSlotsException) { + Log.e(TAG, "Failed to switch to removable slot.") + return + } + SimNotificationService.scheduleSimNotification( + mContext, SimActivationNotifier.NotificationType.SWITCH_TO_REMOVABLE_SLOT + ) + } + + private fun handleSimRemove(removableSlotInfo: UiccSlotInfo) { + Log.i(TAG, "Handle SIM removed.") + + if (!isSuwFinished(mContext!!)) { + Log.i(TAG, "Still in SUW. Handle SIM removal after SUW is finished") + setSuwRemovableSlotAction(mContext!!, LAST_USER_ACTION_IN_SUW_REMOVE) + return + } + + val groupedEmbeddedSubscriptions = + groupedEmbeddedSubscriptions + if (groupedEmbeddedSubscriptions.isEmpty() || !removableSlotInfo.ports.stream() + .findFirst().get().isActive + ) { + Log.i( + TAG, ("eSIM slot is active or no subscriptions exist. Do nothing." + + " The removableSlotInfo: " + removableSlotInfo + + ", groupedEmbeddedSubscriptions: " + groupedEmbeddedSubscriptions) + ) + return + } + + // If there is only 1 eSIM profile exists, we ask the user if they want to switch to that + // profile. + if (groupedEmbeddedSubscriptions.size == 1) { + Log.i(TAG, "Only 1 eSIM profile found. Ask user's consent to switch.") + startSwitchSlotConfirmDialogActivity(groupedEmbeddedSubscriptions[0]) + return + } + + // If there are more than 1 eSIM profiles installed, we show a screen to let users to choose + // the number they want to use. + Log.i(TAG, "Multiple eSIM profiles found. Ask user which subscription to use.") + startChooseSimActivity(false) + } + + private fun hasOtherActiveSubInfo(psimSubId: Int): Boolean { + val activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr) + return activeSubs.stream() + .anyMatch { subscriptionInfo -> subscriptionInfo.subscriptionId != psimSubId } + } + + private fun hasAnyPortActiveInSlot(removableSlotInfo: UiccSlotInfo): Boolean { + return removableSlotInfo.ports.stream().anyMatch { slot -> slot.isActive } + } + + private fun handleRemovableSimInsertWhenDsds(removableSlotInfo: UiccSlotInfo) { + Log.i(TAG, "ForNewUi: Handle Removable SIM inserted") + CoroutineScope(Dispatchers.Default + SupervisorJob()).launch { + withContext(Dispatchers.Default) { + val subscriptionInfos = + withTimeoutOrNull(DEFAULT_WAIT_AFTER_SIM_INSERTED_TIMEOUT_MILLIS) { + SubscriptionRepository(mContext!!) + .removableSubscriptionInfoListFlow() + .firstOrNull { it.isNotEmpty() } + } + + if (subscriptionInfos.isNullOrEmpty()) { + Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.") + return@withContext + } + Log.d( + TAG, + "getAvailableRemovableSubscription:$subscriptionInfos" + ) + val psimSubId = subscriptionInfos[0].subscriptionId + if (!hasAnyPortActiveInSlot(removableSlotInfo) + || hasOtherActiveSubInfo(psimSubId)) { + Log.d(TAG, "ForNewUi Start Setup flow") + startSimConfirmDialogActivity(psimSubId) + } + } + } + } + + private fun handleRemovableSimInsertUnderDsdsMep(removableSlotInfo: UiccSlotInfo) { + Log.i(TAG, "Handle Removable SIM inserted under DSDS+Mep.") + + if (removableSlotInfo.ports.stream().findFirst().get().isActive) { + Log.i( + TAG, "The removable slot is already active. Do nothing. removableSlotInfo: " + + removableSlotInfo + ) + return + } + + val subscriptionInfos = + availableRemovableSubscription + if (subscriptionInfos.isEmpty()) { + Log.e(TAG, "Unable to find the removable subscriptionInfo. Do nothing.") + return + } + Log.d( + TAG, + "getAvailableRemovableSubscription:$subscriptionInfos" + ) + startSimConfirmDialogActivity(subscriptionInfos[0].subscriptionId) + } + + private fun getLastRemovableSimSlotState(context: Context): Int { + val prefs = context.getSharedPreferences(EUICC_PREFS, Context.MODE_PRIVATE) + return prefs.getInt(KEY_REMOVABLE_SLOT_STATE, UiccSlotInfo.CARD_STATE_INFO_ABSENT) + } + + private fun setRemovableSimSlotState(context: Context, state: Int) { + val prefs = context.getSharedPreferences(EUICC_PREFS, Context.MODE_PRIVATE) + prefs.edit().putInt(KEY_REMOVABLE_SLOT_STATE, state).apply() + Log.d(TAG, "setRemovableSimSlotState: $state") + } + + private fun getSuwRemovableSlotAction(context: Context): Int { + val prefs = context.getSharedPreferences(EUICC_PREFS, Context.MODE_PRIVATE) + return prefs.getInt(KEY_SUW_PSIM_ACTION, LAST_USER_ACTION_IN_SUW_NONE) + } + + private fun setSuwRemovableSlotAction(context: Context, action: Int) { + val prefs = context.getSharedPreferences(EUICC_PREFS, Context.MODE_PRIVATE) + prefs.edit().putInt(KEY_SUW_PSIM_ACTION, action).apply() + } + + private val removableUiccSlotInfo: UiccSlotInfo? + get() { + val slotInfos = mTelMgr!!.uiccSlotsInfo + if (slotInfos == null) { + Log.e( + TAG, + "slotInfos is null. Unable to get slot infos." + ) + return null + } + for (slotInfo in slotInfos) { + if (slotInfo != null && slotInfo.isRemovable) { + return slotInfo + } + } + return null + } + + private fun hasActiveEsimSubscription(): Boolean { + val activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr) + return activeSubs.stream().anyMatch { subscriptionInfo -> subscriptionInfo.isEmbedded } + } + + private val groupedEmbeddedSubscriptions: List + get() { + val groupedSubscriptions = + SubscriptionUtil.getSelectableSubscriptionInfoList(mContext) + ?: return ImmutableList.of() + return ImmutableList.copyOf( + groupedSubscriptions.stream() + .filter { sub: SubscriptionInfo -> sub.isEmbedded } + .collect(Collectors.toList())) + } + + protected val availableRemovableSubscription: List + get() { + val removableSubscriptions = + SubscriptionUtil.getAvailableSubscriptions(mContext) + return ImmutableList.copyOf( + removableSubscriptions.stream() + // ToDo: This condition is for psim only. If device supports removable + // esim, it needs an new condition. + .filter { sub: SubscriptionInfo -> !sub.isEmbedded } + .collect(Collectors.toList())) + } + + private fun startChooseSimActivity(psimInserted: Boolean) { + val intent = ChooseSimActivity.getIntent(mContext) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.putExtra(ChooseSimActivity.KEY_HAS_PSIM, psimInserted) + mContext!!.startActivityAsUser(intent, UserHandle.SYSTEM) + } + + private fun startSwitchSlotConfirmDialogActivity(subscriptionInfo: SubscriptionInfo) { + val intent = Intent( + mContext, + SwitchToEsimConfirmDialogActivity::class.java + ) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.putExtra(SwitchToEsimConfirmDialogActivity.KEY_SUB_TO_ENABLE, subscriptionInfo) + mContext!!.startActivityAsUser(intent, UserHandle.SYSTEM) + } + + private fun startDsdsDialogActivity() { + val intent = Intent(mContext, DsdsDialogActivity::class.java) + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + mContext!!.startActivityAsUser(intent, UserHandle.SYSTEM) + } + + private fun startSimConfirmDialogActivity(subId: Int) { + if (!isSuwFinished(mContext!!)) { + Log.d(TAG, "Still in SUW. Do nothing") + return + } + if (!SubscriptionManager.isUsableSubscriptionId(subId)) { + Log.i(TAG, "Unable to enable subscription due to invalid subscription ID.") + return + } + Log.d( + TAG, + "Start ToggleSubscriptionDialogActivity with $subId under DSDS+Mep." + ) + SubscriptionUtil.startToggleSubscriptionDialogActivity(mContext, subId, true, true) + } + + private val isMultipleEnabledProfilesSupported: Boolean + get() { + val cardInfos = mTelMgr!!.uiccCardsInfo + if (cardInfos == null) { + Log.d( + TAG, + "UICC cards info list is empty." + ) + return false + } + return cardInfos.stream() + .anyMatch { cardInfo: UiccCardInfo -> cardInfo.isMultipleEnabledProfilesSupported } + } + + private fun isSuwFinished(context: Context): Boolean { + try { + // DEVICE_PROVISIONED is 0 if still in setup wizard. 1 if setup completed. + return (Settings.Global.getInt( + context.contentResolver, Settings.Global.DEVICE_PROVISIONED) + == 1) + } catch (e: SettingNotFoundException) { + Log.e(TAG, "Cannot get DEVICE_PROVISIONED from the device.", e) + return false + } + } + + companion object { + private const val TAG = "SimSlotChangeHandler" + + private const val EUICC_PREFS = "euicc_prefs" + + // Shared preference keys + private const val KEY_REMOVABLE_SLOT_STATE = "removable_slot_state" + private const val KEY_SUW_PSIM_ACTION = "suw_psim_action" + + // User's last removable SIM insertion / removal action during SUW. + private const val LAST_USER_ACTION_IN_SUW_NONE = 0 + private const val LAST_USER_ACTION_IN_SUW_INSERT = 1 + private const val LAST_USER_ACTION_IN_SUW_REMOVE = 2 + + private const val DEFAULT_WAIT_AFTER_SIM_INSERTED_TIMEOUT_MILLIS: Long = 25 * 1000L + + @Volatile + private var slotChangeHandler: SimSlotChangeHandler? = null + + /** Returns a SIM slot change handler singleton. */ + @JvmStatic + fun get(): SimSlotChangeHandler? { + if (slotChangeHandler == null) { + synchronized(SimSlotChangeHandler::class.java) { + if (slotChangeHandler == null) { + slotChangeHandler = SimSlotChangeHandler() + } + } + } + return slotChangeHandler + } + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java index 5973d265a92..552a4a71a71 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityShortcutPreferenceFragmentTest.java @@ -19,7 +19,6 @@ package com.android.settings.accessibility; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS; import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; -import static com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType; import static com.google.common.truth.Truth.assertThat; @@ -37,7 +36,6 @@ import android.content.Context; import android.content.Intent; import android.icu.text.CaseMap; import android.os.Bundle; -import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.view.LayoutInflater; import android.view.View; @@ -57,7 +55,6 @@ import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragm import com.android.settings.testutils.shadow.ShadowFragment; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; @@ -83,18 +80,11 @@ public class AccessibilityShortcutPreferenceFragmentTest { PLACEHOLDER_PACKAGE_NAME + "tile.placeholder"; private static final ComponentName PLACEHOLDER_COMPONENT_NAME = new ComponentName( PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CLASS_NAME); - private static final ComponentName PLACEHOLDER_TILE_COMPONENT_NAME = new ComponentName( - PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_TILE_CLASS_NAME); - private static final String PLACEHOLDER_TILE_TOOLTIP_CONTENT = - PLACEHOLDER_PACKAGE_NAME + "tooltip_content"; - private static final String PLACEHOLDER_DIALOG_TITLE = "title"; private static final String SOFTWARE_SHORTCUT_KEY = Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS; private static final String HARDWARE_SHORTCUT_KEY = Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private TestAccessibilityShortcutPreferenceFragment mFragment; private PreferenceScreen mScreen; private Context mContext = ApplicationProvider.getApplicationContext(); @@ -151,14 +141,6 @@ public class AccessibilityShortcutPreferenceFragmentTest { assertThat(expectedType).isEqualTo(HARDWARE); } - @Test - @Config(shadows = ShadowFragment.class) - public void showQuickSettingsTooltipIfNeeded_dontShowTooltipView() { - mFragment.showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_EDIT); - - assertThat(getLatestPopupWindow()).isNull(); - } - @Test @Config(shadows = ShadowFragment.class) public void showGeneralCategory_shouldInitCategory() { @@ -260,16 +242,6 @@ public class AccessibilityShortcutPreferenceFragmentTest { return PLACEHOLDER_PACKAGE_NAME; } - @Override - protected ComponentName getTileComponentName() { - return PLACEHOLDER_TILE_COMPONENT_NAME; - } - - @Override - protected CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type) { - return PLACEHOLDER_TILE_TOOLTIP_CONTENT; - } - @Override public int getUserShortcutTypes() { return 0; diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java index 86322f916b0..6dbb8b58354 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragmentTest.java @@ -315,14 +315,6 @@ public class ToggleFeaturePreferenceFragmentTest { .isEqualTo(PLACEHOLDER_COMPONENT_NAME.flattenToString()); } - @Test - @Config(shadows = ShadowFragment.class) - public void showQuickSettingsTooltipIfNeeded_dontShowTooltipView() { - mFragment.showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_EDIT); - - assertThat(getLatestPopupWindow()).isNull(); - } - @Test public void getShortcutTypeSummary_shortcutSummaryIsCorrectlySet() { final PreferredShortcut userPreferredShortcut = new PreferredShortcut( diff --git a/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt new file mode 100644 index 00000000000..ad633cc8c8a --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/display/DisplayTopologyPreferenceTest.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.connecteddevice.display + +import android.hardware.display.DisplayTopology.TreeNode.POSITION_LEFT + +import android.content.Context +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.ColorDrawable +import android.hardware.display.DisplayTopology +import android.view.View +import android.widget.FrameLayout +import androidx.preference.PreferenceViewHolder +import androidx.test.core.app.ApplicationProvider + +import com.android.settings.R +import com.google.common.truth.Truth.assertThat + +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class DisplayTopologyPreferenceTest { + val context = ApplicationProvider.getApplicationContext() + val preference = DisplayTopologyPreference(context) + val injector = TestInjector() + val rootView = View.inflate(context, preference.layoutResource, /*parent=*/ null) + val holder = PreferenceViewHolder.createInstanceForTests(rootView) + val wallpaper = ColorDrawable(Color.MAGENTA) + + init { + preference.injector = injector + injector.systemWallpaper = wallpaper + preference.onBindViewHolder(holder) + } + + class TestInjector : DisplayTopologyPreference.Injector() { + var topology : DisplayTopology? = null + var systemWallpaper : Drawable? = null + + override fun displayTopology(context : Context) : DisplayTopology? { return topology } + + override fun wallpaper(context : Context) : Drawable { return systemWallpaper!! } + } + + @Test + fun disabledTopology() { + preference.onAttached() + preference.onGlobalLayout() + + assertThat(preference.mPaneContent.childCount).isEqualTo(0) + // TODO(b/352648432): update test when we show the main display even when + // a topology is not active. + assertThat(preference.mTopologyHint.text).isEqualTo("") + } + + @Test + fun twoDisplaysGenerateBlocks() { + val child = DisplayTopology.TreeNode( + /* displayId= */ 42, /* width= */ 100f, /* height= */ 80f, + POSITION_LEFT, /* offset= */ 42f) + val root = DisplayTopology.TreeNode( + /* displayId= */ 0, /* width= */ 200f, /* height= */ 160f, + POSITION_LEFT, /* offset= */ 0f) + root.addChild(child) + injector.topology = DisplayTopology(root, /*primaryDisplayId=*/ 0) + + // This layoutParams needs to be non-null for the global layout handler. + preference.mPaneHolder.layoutParams = FrameLayout.LayoutParams( + /* width= */ 640, /* height= */ 480) + + // Force pane width to have a reasonable value (hundreds of dp) so the TopologyScale is + // calculated reasonably. + preference.mPaneContent.left = 0 + preference.mPaneContent.right = 640 + + preference.onAttached() + preference.onGlobalLayout() + + assertThat(preference.mPaneContent.childCount).isEqualTo(2) + val block0 = preference.mPaneContent.getChildAt(0) + val block1 = preference.mPaneContent.getChildAt(1) + + // Block of child display is on the left. + val (childBlock, rootBlock) = if (block0.x < block1.x) + listOf(block0, block1) + else + listOf(block1, block0) + + // After accounting for padding, child should be half the length of root in each dimension. + assertThat(childBlock.layoutParams.width + BLOCK_PADDING) + .isEqualTo(rootBlock.layoutParams.width / 2) + assertThat(childBlock.layoutParams.height + BLOCK_PADDING) + .isEqualTo(rootBlock.layoutParams.height / 2) + assertThat(childBlock.y).isGreaterThan(rootBlock.y) + assertThat(block0.background).isEqualTo(wallpaper) + assertThat(block1.background).isEqualTo(wallpaper) + assertThat(rootBlock.x - BLOCK_PADDING * 2) + .isEqualTo(childBlock.x + childBlock.layoutParams.width) + + assertThat(preference.mTopologyHint.text) + .isEqualTo(context.getString(R.string.external_display_topology_hint)) + } +} diff --git a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java index a03ca6192b6..1ce6c3f5499 100644 --- a/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java +++ b/tests/robotests/src/com/android/settings/gestures/OneHandedSettingsTest.java @@ -21,7 +21,6 @@ import static com.android.settings.gestures.OneHandedSettings.ONE_HANDED_SHORTCU import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import android.content.Context; import android.os.SystemProperties; @@ -32,8 +31,6 @@ import android.provider.SearchIndexableResource; import androidx.test.core.app.ApplicationProvider; -import com.android.settings.R; -import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType; import com.android.settingslib.search.SearchIndexableRaw; import org.junit.Before; @@ -62,21 +59,6 @@ public class OneHandedSettingsTest { SystemProperties.set(OneHandedSettingsUtils.SUPPORT_ONE_HANDED_MODE, "true"); } - @Test - public void getTileTooltipContent_returnsExpectedValues() { - // Simulate to call getTileTooltipContent after onDetach - assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_EDIT)) - .isNull(); - // Simulate to call getTileTooltipContent after onAttach - when(mSettings.getContext()).thenReturn(mContext); - assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_EDIT)) - .isEqualTo(mContext.getText( - R.string.accessibility_one_handed_mode_qs_tooltip_content)); - assertThat(mSettings.getTileTooltipContent(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE)) - .isEqualTo(mContext.getText( - R.string.accessibility_one_handed_mode_auto_added_qs_tooltip_content)); - } - @Test public void getLogTag_returnsCorrectTag() { assertThat(mSettings.getLogTag()).isEqualTo("OneHandedSettings"); diff --git a/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt b/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt index 27b5c382211..c6974abf059 100644 --- a/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt +++ b/tests/unit/src/com/android/settings/network/telephony/CellInfoUtilTest.kt @@ -24,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle import com.android.settings.network.telephony.CellInfoUtil.getOperatorNumeric import com.google.common.truth.Truth.assertThat +import org.junit.Ignore import org.junit.Test import org.junit.runner.RunWith @@ -95,6 +96,7 @@ class CellInfoUtilTest { } @Test + @Ignore("b/383858953") fun cellInfoListToString() { val cellInfoList = listOf(