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(