Snap for 12809477 from 7b7f72d902 to 25Q2-release

Change-Id: Id0edeea642b5a121f56fb57fd8ee45fceacc0666
This commit is contained in:
Android Build Coastguard Worker
2024-12-16 21:58:49 -08:00
21 changed files with 797 additions and 705 deletions

View File

@@ -36,8 +36,7 @@
android:id="@+id/battery_chart_group" android:id="@+id/battery_chart_group"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical">
android:alpha="0">
<com.android.settings.fuelgauge.batteryusage.BatteryChartView <com.android.settings.fuelgauge.batteryusage.BatteryChartView
android:id="@+id/daily_battery_chart" android:id="@+id/daily_battery_chart"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -53,7 +52,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/chartview_layout_height" android:layout_height="@dimen/chartview_layout_height"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:visibility="visible" android:visibility="gone"
android:contentDescription="@string/hourly_battery_usage_chart" android:contentDescription="@string/hourly_battery_usage_chart"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
settings:textColor="?android:attr/textColorSecondary" /> settings:textColor="?android:attr/textColorSecondary" />

View File

@@ -16,8 +16,9 @@
<FrameLayout <FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/display_topology_pane_holder"
android:importantForAccessibility="no" android:importantForAccessibility="no"
android:layout_height="160dp" android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:paddingHorizontal="@dimen/display_topology_pane_margin" android:paddingHorizontal="@dimen/display_topology_pane_margin"
android:orientation="horizontal"> android:orientation="horizontal">
@@ -27,14 +28,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:src="@drawable/display_topology_background"/> android:src="@drawable/display_topology_background"/>
<FrameLayout <FrameLayout
android:id="@+id/display_topology_container" android:id="@+id/display_topology_pane_content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"/> android:layout_height="match_parent"/>
<TextView <TextView
android:id="@+id/topology_hint" android:id="@+id/topology_hint"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_gravity="top|center_horizontal" android:layout_gravity="top|center_horizontal"
android:paddingTop="10dp" android:paddingBottom="10dp"
android:text="@string/external_display_topology_hint"/> android:paddingTop="10dp" />
</FrameLayout> </FrameLayout>

View File

@@ -96,17 +96,6 @@ public class AccessibilityHearingAidsFragment extends AccessibilityShortcutPrefe
return mFeatureName; 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 @Override
protected boolean showGeneralCategory() { protected boolean showGeneralCategory() {
// Have static preference under dynamically created PreferenceCategory KEY_GENERAL_CATEGORY. // Have static preference under dynamically created PreferenceCategory KEY_GENERAL_CATEGORY.

View File

@@ -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.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList; 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_GENERAL_CATEGORY;
import static com.android.settings.accessibility.ToggleFeaturePreferenceFragment.KEY_SAVED_QS_TOOLTIP_TYPE;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.ComponentName; import android.content.ComponentName;
@@ -44,7 +42,6 @@ import androidx.preference.PreferenceScreen;
import com.android.internal.accessibility.common.ShortcutConstants; import com.android.internal.accessibility.common.ShortcutConstants;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment; import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragment;
import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.dashboard.RestrictedDashboardFragment;
@@ -60,16 +57,12 @@ import java.util.Set;
public abstract class AccessibilityShortcutPreferenceFragment extends RestrictedDashboardFragment public abstract class AccessibilityShortcutPreferenceFragment extends RestrictedDashboardFragment
implements ShortcutPreference.OnClickCallback { implements ShortcutPreference.OnClickCallback {
private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; 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 ShortcutPreference mShortcutPreference;
protected Dialog mDialog; protected Dialog mDialog;
private AccessibilityManager.TouchExplorationStateChangeListener private AccessibilityManager.TouchExplorationStateChangeListener
mTouchExplorationStateChangeListener; mTouchExplorationStateChangeListener;
private AccessibilitySettingsContentObserver mSettingsContentObserver; private AccessibilitySettingsContentObserver mSettingsContentObserver;
private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
private boolean mNeedsQSTooltipReshow = false;
private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
public AccessibilityShortcutPreferenceFragment(String restrictionKey) { public AccessibilityShortcutPreferenceFragment(String restrictionKey) {
super(restrictionKey); super(restrictionKey);
@@ -81,26 +74,10 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
/** Returns the accessibility feature name. */ /** Returns the accessibility feature name. */
protected abstract CharSequence getLabelName(); 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(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(); final int resId = getPreferenceScreenResId();
if (resId <= 0) { if (resId <= 0) {
final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen( final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
@@ -141,21 +118,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
return super.onCreateView(inflater, container, savedInstanceState); 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 @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@@ -177,16 +139,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
super.onPause(); 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 @Override
public Dialog onCreateDialog(int dialogId) { public Dialog onCreateDialog(int dialogId) {
switch (dialogId) { switch (dialogId) {
@@ -289,7 +241,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
*/ */
private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) { private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) {
dialog.dismiss(); dialog.dismiss();
showQuickSettingsTooltipIfNeeded();
} }
@VisibleForTesting @VisibleForTesting
@@ -362,26 +313,6 @@ public abstract class AccessibilityShortcutPreferenceFragment extends Restricted
mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); 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 * Returns the user preferred shortcut types or the default shortcut types if not set
*/ */

View File

@@ -71,11 +71,6 @@ public class LaunchAccessibilityActivityPreferenceFragment extends ToggleFeature
return view; return view;
} }
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
// Do nothing.
}
@Override @Override
protected void onProcessArguments(Bundle arguments) { protected void onProcessArguments(Bundle arguments) {
super.onProcessArguments(arguments); super.onProcessArguments(arguments);

View File

@@ -107,10 +107,6 @@ public class ToggleColorInversionPreferenceFragment extends ToggleFeaturePrefere
if (enabled == isEnabled) { if (enabled == isEnabled) {
return; return;
} }
if (enabled) {
showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
}
logAccessibilityServiceEnabled(mComponentName, enabled); logAccessibilityServiceEnabled(mComponentName, enabled);
Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF); Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF);
} }

View File

@@ -158,10 +158,6 @@ public class ToggleDaltonizerPreferenceFragment extends ToggleFeaturePreferenceF
if (enabled == isEnabled) { if (enabled == isEnabled) {
return; return;
} }
if (enabled) {
showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
}
logAccessibilityServiceEnabled(mComponentName, enabled); logAccessibilityServiceEnabled(mComponentName, enabled);
Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF); Settings.Secure.putInt(getContentResolver(), ENABLED, enabled ? ON : OFF);
} }

View File

@@ -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.AccessibilityDialogUtils.DialogEnums;
import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList; import static com.android.settings.accessibility.AccessibilityUtil.getShortcutSummaryList;
import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.ComponentName; import android.content.ComponentName;
@@ -85,8 +84,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
public static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; public static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference";
protected static final String KEY_TOP_INTRO_PREFERENCE = "top_intro"; 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_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"; protected static final String KEY_ANIMATED_IMAGE = "animated_image";
// For html description of accessibility service, must follow the rule, such as // For html description of accessibility service, must follow the rule, such as
// <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully. // <img src="R.drawable.fileName"/>, a11y settings will get the resources successfully.
@@ -112,10 +109,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
private CharSequence mDescription; private CharSequence mDescription;
private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener;
private AccessibilitySettingsContentObserver mSettingsContentObserver; private AccessibilitySettingsContentObserver mSettingsContentObserver;
private AccessibilityQuickSettingsTooltipWindow mTooltipWindow;
private boolean mNeedsQSTooltipReshow = false;
private int mNeedsQSTooltipType = QuickSettingsTooltipType.GUIDE_TO_EDIT;
private ImageView mImageGetterCacheView; private ImageView mImageGetterCacheView;
protected final Html.ImageGetter mImageGetter = (String str) -> { protected final Html.ImageGetter mImageGetter = (String str) -> {
if (str != null && str.startsWith(IMG_PREFIX)) { if (str != null && str.startsWith(IMG_PREFIX)) {
@@ -133,16 +126,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
onProcessArguments(getArguments()); 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(); final int resId = getPreferenceScreenResId();
if (resId <= 0) { if (resId <= 0) {
final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen( final PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
@@ -227,16 +210,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
final SettingsMainSwitchBar switchBar = settingsActivity.getSwitchBar(); final SettingsMainSwitchBar switchBar = settingsActivity.getSwitchBar();
switchBar.hide(); 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()); writeConfigDefaultAccessibilityServiceIntoShortcutTargetServiceIfNeeded(getContext());
} }
@@ -261,24 +234,10 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
super.onPause(); 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 @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
removeActionBarToggleSwitch(); removeActionBarToggleSwitch();
final boolean isTooltipWindowShowing = mTooltipWindow != null && mTooltipWindow.isShowing();
if (isTooltipWindowShowing) {
mTooltipWindow.dismiss();
}
} }
@Override @Override
@@ -314,7 +273,12 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
/** Returns the accessibility tile component name. */ /** Returns the accessibility tile component name. */
abstract ComponentName getTileComponentName(); 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); abstract CharSequence getTileTooltipContent(@QuickSettingsTooltipType int type);
protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) { protected void updateToggleServiceTitle(SettingsMainSwitchPreference switchPreference) {
@@ -332,9 +296,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
} }
protected void onPreferenceToggled(String preferenceKey, boolean enabled) { protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
if (enabled) {
showQuickSettingsTooltipIfNeeded();
}
} }
protected void onInstallSwitchPreferenceToggleSwitch() { protected void onInstallSwitchPreferenceToggleSwitch() {
@@ -646,7 +607,6 @@ public abstract class ToggleFeaturePreferenceFragment extends DashboardFragment
*/ */
private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) { private void callOnTutorialDialogButtonClicked(DialogInterface dialog, int which) {
dialog.dismiss(); dialog.dismiss();
showQuickSettingsTooltipIfNeeded();
} }
protected void updateShortcutPreferenceData() { 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}. */ /** Returns user visible name of the tile by given {@link ComponentName}. */
protected CharSequence loadTileLabel(Context context, ComponentName componentName) { protected CharSequence loadTileLabel(Context context, ComponentName componentName) {
final PackageManager packageManager = context.getPackageManager(); final PackageManager packageManager = context.getPackageManager();

View File

@@ -149,9 +149,6 @@ public class ToggleReduceBrightColorsPreferenceFragment extends ToggleFeaturePre
@Override @Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) { protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
if (enabled) {
showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
}
logAccessibilityServiceEnabled(mComponentName, enabled); logAccessibilityServiceEnabled(mComponentName, enabled);
mColorDisplayManager.setReduceBrightColorsActivated(enabled); mColorDisplayManager.setReduceBrightColorsActivated(enabled);
} }

View File

@@ -16,14 +16,32 @@
package com.android.settings.connecteddevice.display package com.android.settings.connecteddevice.display
import android.app.WallpaperManager
import com.android.settings.R import com.android.settings.R
import android.content.Context import android.content.Context
import android.graphics.Color
import android.graphics.Point import android.graphics.Point
import android.graphics.PointF import android.graphics.PointF
import android.graphics.RectF 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.Preference
import androidx.preference.PreferenceViewHolder
import java.util.Locale import java.util.Locale
@@ -143,11 +161,27 @@ class TopologyScale(
const val PREFERENCE_KEY = "display_topology_preference" 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 * DisplayTopologyPreference allows the user to change the display topology
* when there is one or more extended display attached. * 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 { init {
layoutResource = R.layout.display_topology_preference layoutResource = R.layout.display_topology_preference
@@ -155,5 +189,108 @@ class DisplayTopologyPreference(context : Context) : Preference(context) {
isSelectable = false isSelectable = false
key = PREFERENCE_KEY 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<Int, RectF>, 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()
}
}
} }
} }

View File

@@ -274,7 +274,6 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
} }
if (mDailyChartView != dailyChartView || mHourlyChartView != hourlyChartView) { if (mDailyChartView != dailyChartView || mHourlyChartView != hourlyChartView) {
mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView)); mHandler.post(() -> setBatteryChartViewInner(dailyChartView, hourlyChartView));
animateBatteryChartViewGroup();
} }
if (mBatteryChartViewGroup != null) { if (mBatteryChartViewGroup != null) {
final View grandparentView = (View) mBatteryChartViewGroup.getParent(); final View grandparentView = (View) mBatteryChartViewGroup.getParent();

View File

@@ -22,9 +22,9 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CompoundButton;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@@ -33,7 +33,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.accessibility.AccessibilityFragmentUtils; import com.android.settings.accessibility.AccessibilityFragmentUtils;
import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment; import com.android.settings.accessibility.AccessibilityShortcutPreferenceFragment;
import com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexable;
import com.android.settingslib.search.SearchIndexableRaw; import com.android.settingslib.search.SearchIndexableRaw;
@@ -82,12 +81,7 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment {
final MainSwitchPreference mainSwitchPreference = final MainSwitchPreference mainSwitchPreference =
getPreferenceScreen().findPreference(ONE_HANDED_MAIN_SWITCH_KEY); getPreferenceScreen().findPreference(ONE_HANDED_MAIN_SWITCH_KEY);
mainSwitchPreference.addOnSwitchChangeListener((switchView, isChecked) -> { mainSwitchPreference.addOnSwitchChangeListener(CompoundButton::setChecked);
switchView.setChecked(isChecked);
if (isChecked) {
showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_DIRECT_USE);
}
});
} }
@Override @Override
@@ -145,24 +139,6 @@ public class OneHandedSettings extends AccessibilityShortcutPreferenceFragment {
return mFeatureName; 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 @Override
protected int getPreferenceScreenResId() { protected int getPreferenceScreenResId() {
return R.xml.one_handed_settings; return R.xml.one_handed_settings;

View File

@@ -40,6 +40,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.shareIn
import java.util.stream.Collectors
private const val TAG = "SubscriptionRepository" private const val TAG = "SubscriptionRepository"
@@ -154,6 +155,18 @@ class SubscriptionRepository(private val context: Context) {
.conflate() .conflate()
.flowOn(Dispatchers.Default) .flowOn(Dispatchers.Default)
fun removableSubscriptionInfoListFlow(): Flow<List<SubscriptionInfo>> {
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) @OptIn(ExperimentalCoroutinesApi::class)
fun phoneNumberFlow(subId: Int): Flow<String?> = fun phoneNumberFlow(subId: Int): Flow<String?> =
activeSubscriptionInfoFlow(subId).flatMapLatest { subInfo -> activeSubscriptionInfoFlow(subId).flatMapLatest { subInfo ->

View File

@@ -19,7 +19,6 @@ package com.android.settings.service
import android.app.Application import android.app.Application
import android.os.Binder import android.os.Binder
import android.os.OutcomeReceiver import android.os.OutcomeReceiver
import android.os.Process
import android.service.settings.preferences.GetValueRequest import android.service.settings.preferences.GetValueRequest
import android.service.settings.preferences.GetValueResult import android.service.settings.preferences.GetValueResult
import android.service.settings.preferences.MetadataRequest import android.service.settings.preferences.MetadataRequest
@@ -37,7 +36,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.lang.Exception
class PreferenceService : SettingsPreferenceService() { class PreferenceService : SettingsPreferenceService() {
@@ -49,14 +47,22 @@ class PreferenceService : SettingsPreferenceService() {
override fun onGetAllPreferenceMetadata( override fun onGetAllPreferenceMetadata(
request: MetadataRequest, request: MetadataRequest,
callback: OutcomeReceiver<MetadataResult, Exception> callback: OutcomeReceiver<MetadataResult, Exception>,
) { ) {
// MUST get pid/uid in binder thread
val callingPid = Binder.getCallingPid()
val callingUid = Binder.getCallingUid()
scope.launch { scope.launch {
val graphProto = graphApi.invoke(application, Process.myUid(), Binder.getCallingUid(), val graphProto =
GetPreferenceGraphRequest( graphApi.invoke(
includeValue = false, application,
flags = PreferenceGetterFlags.METADATA callingPid,
)) callingUid,
GetPreferenceGraphRequest(
includeValue = false,
flags = PreferenceGetterFlags.METADATA,
),
)
val result = transformCatalystGetMetadataResponse(this@PreferenceService, graphProto) val result = transformCatalystGetMetadataResponse(this@PreferenceService, graphProto)
callback.onResult(result) callback.onResult(result)
} }
@@ -64,17 +70,16 @@ class PreferenceService : SettingsPreferenceService() {
override fun onGetPreferenceValue( override fun onGetPreferenceValue(
request: GetValueRequest, request: GetValueRequest,
callback: OutcomeReceiver<GetValueResult, Exception> callback: OutcomeReceiver<GetValueResult, Exception>,
) { ) {
// MUST get pid/uid in binder thread
val callingPid = Binder.getCallingPid()
val callingUid = Binder.getCallingUid()
scope.launch { scope.launch {
val apiRequest = transformFrameworkGetValueRequest(request) val apiRequest = transformFrameworkGetValueRequest(request)
val response = getApiHandler.invoke(application, Process.myUid(), val response = getApiHandler.invoke(application, callingPid, callingUid, apiRequest)
Binder.getCallingUid(), apiRequest) val result =
val result = transformCatalystGetValueResponse( transformCatalystGetValueResponse(this@PreferenceService, request, response)
this@PreferenceService,
request,
response
)
if (result == null) { if (result == null) {
callback.onError(IllegalStateException("No response")) callback.onError(IllegalStateException("No response"))
} else { } else {
@@ -85,8 +90,11 @@ class PreferenceService : SettingsPreferenceService() {
override fun onSetPreferenceValue( override fun onSetPreferenceValue(
request: SetValueRequest, request: SetValueRequest,
callback: OutcomeReceiver<SetValueResult, Exception> callback: OutcomeReceiver<SetValueResult, Exception>,
) { ) {
// MUST get pid/uid in binder thread
val callingPid = Binder.getCallingPid()
val callingUid = Binder.getCallingUid()
scope.launch { scope.launch {
val apiRequest = transformFrameworkSetValueRequest(request) val apiRequest = transformFrameworkSetValueRequest(request)
if (apiRequest == null) { if (apiRequest == null) {
@@ -94,8 +102,7 @@ class PreferenceService : SettingsPreferenceService() {
SetValueResult.Builder(SetValueResult.RESULT_INVALID_REQUEST).build() SetValueResult.Builder(SetValueResult.RESULT_INVALID_REQUEST).build()
) )
} else { } else {
val response = setApiHandler.invoke(application, Process.myUid(), val response = setApiHandler.invoke(application, callingPid, callingUid, apiRequest)
Binder.getCallingUid(), apiRequest)
callback.onResult(transformCatalystSetValueResponse(response)) callback.onResult(transformCatalystSetValueResponse(response))
} }
@@ -106,9 +113,9 @@ class PreferenceService : SettingsPreferenceService() {
private class GraphProvider(override val id: Int) : GetPreferenceGraphApiHandler(emptySet()) { private class GraphProvider(override val id: Int) : GetPreferenceGraphApiHandler(emptySet()) {
override fun hasPermission( override fun hasPermission(
application: Application, application: Application,
myUid: Int, callingPid: Int,
callingUid: Int, callingUid: Int,
request: GetPreferenceGraphRequest request: GetPreferenceGraphRequest,
) = true ) = true
} }
} }

View File

@@ -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<SubscriptionInfo> 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<SubscriptionInfo> 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<SubscriptionInfo> 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<SubscriptionInfo> 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<SubscriptionInfo> activeSubs = SubscriptionUtil.getActiveSubscriptions(mSubMgr);
return activeSubs.stream().anyMatch(SubscriptionInfo::isEmbedded);
}
private List<SubscriptionInfo> getGroupedEmbeddedSubscriptions() {
List<SubscriptionInfo> groupedSubscriptions =
SubscriptionUtil.getSelectableSubscriptionInfoList(mContext);
if (groupedSubscriptions == null) {
return ImmutableList.of();
}
return ImmutableList.copyOf(
groupedSubscriptions.stream()
.filter(sub -> sub.isEmbedded())
.collect(Collectors.toList()));
}
protected List<SubscriptionInfo> getAvailableRemovableSubscription() {
List<SubscriptionInfo> 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<UiccCardInfo> 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() {}
}

View File

@@ -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<SubscriptionInfo>
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<SubscriptionInfo>
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
}
}
}

View File

@@ -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.HARDWARE;
import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.QUICK_SETTINGS; 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.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE;
import static com.android.settings.accessibility.AccessibilityUtil.QuickSettingsTooltipType;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@@ -37,7 +36,6 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.icu.text.CaseMap; import android.icu.text.CaseMap;
import android.os.Bundle; import android.os.Bundle;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings; import android.provider.Settings;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -57,7 +55,6 @@ import com.android.settings.accessibility.shortcuts.EditShortcutsPreferenceFragm
import com.android.settings.testutils.shadow.ShadowFragment; import com.android.settings.testutils.shadow.ShadowFragment;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers; import org.mockito.Answers;
@@ -83,18 +80,11 @@ public class AccessibilityShortcutPreferenceFragmentTest {
PLACEHOLDER_PACKAGE_NAME + "tile.placeholder"; PLACEHOLDER_PACKAGE_NAME + "tile.placeholder";
private static final ComponentName PLACEHOLDER_COMPONENT_NAME = new ComponentName( private static final ComponentName PLACEHOLDER_COMPONENT_NAME = new ComponentName(
PLACEHOLDER_PACKAGE_NAME, PLACEHOLDER_CLASS_NAME); 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 = private static final String SOFTWARE_SHORTCUT_KEY =
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS; Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS;
private static final String HARDWARE_SHORTCUT_KEY = private static final String HARDWARE_SHORTCUT_KEY =
Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE; Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
private TestAccessibilityShortcutPreferenceFragment mFragment; private TestAccessibilityShortcutPreferenceFragment mFragment;
private PreferenceScreen mScreen; private PreferenceScreen mScreen;
private Context mContext = ApplicationProvider.getApplicationContext(); private Context mContext = ApplicationProvider.getApplicationContext();
@@ -151,14 +141,6 @@ public class AccessibilityShortcutPreferenceFragmentTest {
assertThat(expectedType).isEqualTo(HARDWARE); assertThat(expectedType).isEqualTo(HARDWARE);
} }
@Test
@Config(shadows = ShadowFragment.class)
public void showQuickSettingsTooltipIfNeeded_dontShowTooltipView() {
mFragment.showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_EDIT);
assertThat(getLatestPopupWindow()).isNull();
}
@Test @Test
@Config(shadows = ShadowFragment.class) @Config(shadows = ShadowFragment.class)
public void showGeneralCategory_shouldInitCategory() { public void showGeneralCategory_shouldInitCategory() {
@@ -260,16 +242,6 @@ public class AccessibilityShortcutPreferenceFragmentTest {
return PLACEHOLDER_PACKAGE_NAME; 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 @Override
public int getUserShortcutTypes() { public int getUserShortcutTypes() {
return 0; return 0;

View File

@@ -315,14 +315,6 @@ public class ToggleFeaturePreferenceFragmentTest {
.isEqualTo(PLACEHOLDER_COMPONENT_NAME.flattenToString()); .isEqualTo(PLACEHOLDER_COMPONENT_NAME.flattenToString());
} }
@Test
@Config(shadows = ShadowFragment.class)
public void showQuickSettingsTooltipIfNeeded_dontShowTooltipView() {
mFragment.showQuickSettingsTooltipIfNeeded(QuickSettingsTooltipType.GUIDE_TO_EDIT);
assertThat(getLatestPopupWindow()).isNull();
}
@Test @Test
public void getShortcutTypeSummary_shortcutSummaryIsCorrectlySet() { public void getShortcutTypeSummary_shortcutSummaryIsCorrectlySet() {
final PreferredShortcut userPreferredShortcut = new PreferredShortcut( final PreferredShortcut userPreferredShortcut = new PreferredShortcut(

View File

@@ -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<Context>()
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))
}
}

View File

@@ -21,7 +21,6 @@ import static com.android.settings.gestures.OneHandedSettings.ONE_HANDED_SHORTCU
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context; import android.content.Context;
import android.os.SystemProperties; import android.os.SystemProperties;
@@ -32,8 +31,6 @@ import android.provider.SearchIndexableResource;
import androidx.test.core.app.ApplicationProvider; 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 com.android.settingslib.search.SearchIndexableRaw;
import org.junit.Before; import org.junit.Before;
@@ -62,21 +59,6 @@ public class OneHandedSettingsTest {
SystemProperties.set(OneHandedSettingsUtils.SUPPORT_ONE_HANDED_MODE, "true"); 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 @Test
public void getLogTag_returnsCorrectTag() { public void getLogTag_returnsCorrectTag() {
assertThat(mSettings.getLogTag()).isEqualTo("OneHandedSettings"); assertThat(mSettings.getLogTag()).isEqualTo("OneHandedSettings");

View File

@@ -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.getNetworkTitle
import com.android.settings.network.telephony.CellInfoUtil.getOperatorNumeric import com.android.settings.network.telephony.CellInfoUtil.getOperatorNumeric
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.Ignore
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -95,6 +96,7 @@ class CellInfoUtilTest {
} }
@Test @Test
@Ignore("b/383858953")
fun cellInfoListToString() { fun cellInfoListToString() {
val cellInfoList = val cellInfoList =
listOf( listOf(