From a970f6051b8c69f680a2d23c0ae232d9527f7850 Mon Sep 17 00:00:00 2001 From: Matt Pape Date: Mon, 25 Mar 2019 11:07:16 -0700 Subject: [PATCH 01/43] Remove interface for Telephony. Per API council feedback, we are making changes to include only the namespace in the system API defined in DeviceConfig.java. Strings which define property names should be defined in code local to the feature instead. Bug: 126411407 Test: atest FrameworksCoreTests:DeviceConfigTest Change-Id: I4fc2dc6b4f6876962a9f41d887124016e547f4fa --- .../notification/VibrateWhenRingPreferenceController.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/notification/VibrateWhenRingPreferenceController.java b/src/com/android/settings/notification/VibrateWhenRingPreferenceController.java index cbf909f5e89..b043cb10f39 100644 --- a/src/com/android/settings/notification/VibrateWhenRingPreferenceController.java +++ b/src/com/android/settings/notification/VibrateWhenRingPreferenceController.java @@ -39,6 +39,8 @@ import com.android.settingslib.core.lifecycle.events.OnResume; public class VibrateWhenRingPreferenceController extends TogglePreferenceController implements LifecycleObserver, OnResume, OnPause { + /** Flag for whether or not to apply ramping ringer on incoming phone calls. */ + private static final String RAMPING_RINGER_ENABLED = "ramping_ringer_enabled"; private static final String KEY_VIBRATE_WHEN_RINGING = "vibrate_when_ringing"; private final int DEFAULT_VALUE = 0; private final int NOTIFICATION_VIBRATE_WHEN_RINGING = 1; @@ -130,8 +132,8 @@ public class VibrateWhenRingPreferenceController extends TogglePreferenceControl } private boolean isRampingRingerEnabled() { - return DeviceConfig.getBoolean(DeviceConfig.Telephony.NAMESPACE, - DeviceConfig.Telephony.RAMPING_RINGER_ENABLED, false); + return DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_TELEPHONY, RAMPING_RINGER_ENABLED, false); } } From 8ae9717226285c1f228977f50dd92d1db7fb2e20 Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Tue, 26 Mar 2019 16:07:02 +0800 Subject: [PATCH 02/43] Fix Wi-Fi Easy Connect QR code scanner problems 1. When camera area overlaps display cutout, it's onLayout size is 0/0. To fix it, use ConstraintLayout to specify width and height in layout file. 2. When scanned a QR code and there is an error, set camera area frame line back to unfocused color. Bug: 124399763 Test: manual test Change-Id: I7575efd0ccfb58d114acc3dcca6978d68b801f91 --- .../wifi_dpp_qrcode_scanner_fragment.xml | 4 +- .../wifi_dpp_qrcode_scanner_fragment.xml | 15 +++--- .../dpp/WifiDppQrCodeScannerFragment.java | 1 + .../settings/wifi/qrcode/QrPreviewLayout.java | 49 ------------------- 4 files changed, 12 insertions(+), 57 deletions(-) delete mode 100644 src/com/android/settings/wifi/qrcode/QrPreviewLayout.java diff --git a/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml b/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml index c408a977106..887d0fbfe91 100644 --- a/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml +++ b/res/layout-land/wifi_dpp_qrcode_scanner_fragment.xml @@ -43,7 +43,7 @@ android:layout_marginBottom="8dp" style="?android:attr/progressBarStyleHorizontal"/> - - + - + + android:layout_height="0dp" + app:layout_constraintDimensionRatio="1:1"/> - + android:layout_height="0dp" + app:layout_constraintDimensionRatio="1:1"/> + MeasureSpec.getSize(heightMeasureSpec)) { - super.onMeasure(heightMeasureSpec, heightMeasureSpec); - } else { - super.onMeasure(widthMeasureSpec, widthMeasureSpec); - } - } -} From d69b86a0ef9659f4ca0126c111698505f0626346 Mon Sep 17 00:00:00 2001 From: pastychang Date: Tue, 26 Mar 2019 19:48:13 +0800 Subject: [PATCH 03/43] Copy extra of suw flow to the intent of lock screen pages Bug: 129323340 Test: atest Change-Id: I3886c097bd49df9e1169f8004889f9af8c567560 --- src/com/android/settings/SetupWizardUtils.java | 5 +---- .../src/com/android/settings/SetupWizardUtilsTest.java | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/SetupWizardUtils.java b/src/com/android/settings/SetupWizardUtils.java index 70ee2ca0bf5..166e0655f1d 100644 --- a/src/com/android/settings/SetupWizardUtils.java +++ b/src/com/android/settings/SetupWizardUtils.java @@ -67,9 +67,6 @@ public class SetupWizardUtils { } public static void copySetupExtras(Intent fromIntent, Intent toIntent) { - toIntent.putExtra(WizardManagerHelper.EXTRA_THEME, - fromIntent.getStringExtra(WizardManagerHelper.EXTRA_THEME)); - toIntent.putExtra(WizardManagerHelper.EXTRA_USE_IMMERSIVE_MODE, - fromIntent.getBooleanExtra(WizardManagerHelper.EXTRA_USE_IMMERSIVE_MODE, false)); + WizardManagerHelper.copyWizardManagerExtras(fromIntent, toIntent); } } diff --git a/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java b/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java index f718a67412c..4fe8fa8a466 100644 --- a/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java +++ b/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java @@ -36,12 +36,12 @@ public class SetupWizardUtilsTest { Intent fromIntent = new Intent(); final String theme = "TEST_THEME"; fromIntent.putExtra(WizardManagerHelper.EXTRA_THEME, theme); - fromIntent.putExtra(WizardManagerHelper.EXTRA_USE_IMMERSIVE_MODE, true); + fromIntent.putExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, true); Intent toIntent = new Intent(); SetupWizardUtils.copySetupExtras(fromIntent, toIntent); assertThat(theme).isEqualTo(toIntent.getStringExtra(WizardManagerHelper.EXTRA_THEME)); - assertThat(toIntent.getBooleanExtra(WizardManagerHelper.EXTRA_USE_IMMERSIVE_MODE, false)) + assertThat(toIntent.getBooleanExtra(WizardManagerHelper.EXTRA_IS_SETUP_FLOW, false)) .isTrue(); } From 9afd28fcb4225fc985e9b1ba2d7ecb56d551389a Mon Sep 17 00:00:00 2001 From: timhypeng Date: Mon, 25 Mar 2019 15:50:09 +0800 Subject: [PATCH 04/43] [Fix] Unable to show output device indicator after reboot - To fix the NPE in MediaOutputIndicatorWorker - To get Bluetooth device status in Slice, not in Worker - To purify Worker to be an event receiver Bug: 128945026 Test: make -j42 RunSettingsRoboTests Change-Id: I9360202870c7a43a79641c5b84210c1bfad4b9a6 --- .../media/MediaOutputIndicatorSlice.java | 96 +++++++++++++-- .../media/MediaOutputIndicatorWorker.java | 82 +------------ .../media/MediaOutputIndicatorSliceTest.java | 113 ++++++++++++------ .../media/MediaOutputIndicatorWorkerTest.java | 113 ++---------------- 4 files changed, 173 insertions(+), 231 deletions(-) diff --git a/src/com/android/settings/media/MediaOutputIndicatorSlice.java b/src/com/android/settings/media/MediaOutputIndicatorSlice.java index eb0c81f1c2c..0023e3fb496 100644 --- a/src/com/android/settings/media/MediaOutputIndicatorSlice.java +++ b/src/com/android/settings/media/MediaOutputIndicatorSlice.java @@ -20,35 +20,51 @@ import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDIC import android.annotation.ColorInt; import android.app.PendingIntent; +import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.util.Log; -import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; import androidx.slice.builders.SliceAction; +import com.android.internal.util.CollectionUtils; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.slices.CustomSliceable; -import com.android.settings.slices.SliceBackgroundWorker; +import com.android.settingslib.bluetooth.A2dpProfile; +import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.media.MediaOutputSliceConstants; +import java.util.ArrayList; +import java.util.List; + public class MediaOutputIndicatorSlice implements CustomSliceable { + private static final String TAG = "MediaOutputIndicatorSlice"; + private Context mContext; - @VisibleForTesting - MediaOutputIndicatorWorker mWorker; + private LocalBluetoothManager mLocalBluetoothManager; + private LocalBluetoothProfileManager mProfileManager; public MediaOutputIndicatorSlice(Context context) { mContext = context; + mLocalBluetoothManager = com.android.settings.bluetooth.Utils.getLocalBtManager(context); + if (mLocalBluetoothManager == null) { + Log.e(TAG, "Bluetooth is not supported on this device"); + return; + } + mProfileManager = mLocalBluetoothManager.getProfileManager(); } @Override public Slice getSlice() { - if (!getWorker().isVisible()) { + if (!isVisible()) { return null; } final IconCompat icon = IconCompat.createWithResource(mContext, @@ -66,18 +82,11 @@ public class MediaOutputIndicatorSlice implements CustomSliceable { .setAccentColor(color) .addRow(new ListBuilder.RowBuilder() .setTitle(title) - .setSubtitle(getWorker().findActiveDeviceName()) + .setSubtitle(findActiveDeviceName()) .setPrimaryAction(primarySliceAction)); return listBuilder.build(); } - private MediaOutputIndicatorWorker getWorker() { - if (mWorker == null) { - mWorker = (MediaOutputIndicatorWorker) SliceBackgroundWorker.getInstance(getUri()); - } - return mWorker; - } - private Intent getMediaOutputSliceIntent() { final Intent intent = new Intent() .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) @@ -101,4 +110,65 @@ public class MediaOutputIndicatorSlice implements CustomSliceable { public Class getBackgroundWorkerClass() { return MediaOutputIndicatorWorker.class; } + + private boolean isVisible() { + // To decide Slice's visibility. + // return true if device is connected or previously connected, false for other cases. + return !CollectionUtils.isEmpty(getConnectableA2dpDevices()) + || !CollectionUtils.isEmpty(getConnectableHearingAidDevices()); + } + + private List getConnectableA2dpDevices() { + // Get A2dp devices on all states + // (STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING) + final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); + if (a2dpProfile == null) { + return new ArrayList<>(); + } + return a2dpProfile.getConnectableDevices(); + } + + private List getConnectableHearingAidDevices() { + // Get hearing aid profile devices on all states + // (STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING) + final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile(); + if (hapProfile == null) { + return new ArrayList<>(); + } + + return hapProfile.getConnectableDevices(); + } + + private CharSequence findActiveDeviceName() { + // Return Hearing Aid device name if it is active + BluetoothDevice activeDevice = findActiveHearingAidDevice(); + if (activeDevice != null) { + return activeDevice.getAliasName(); + } + // Return A2DP device name if it is active + final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); + if (a2dpProfile != null) { + activeDevice = a2dpProfile.getActiveDevice(); + if (activeDevice != null) { + return activeDevice.getAliasName(); + } + } + // No active device, return default summary + return mContext.getText(R.string.media_output_default_summary); + } + + private BluetoothDevice findActiveHearingAidDevice() { + final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); + if (hearingAidProfile == null) { + return null; + } + + final List activeDevices = hearingAidProfile.getActiveDevices(); + for (BluetoothDevice btDevice : activeDevices) { + if (btDevice != null) { + return btDevice; + } + } + return null; + } } diff --git a/src/com/android/settings/media/MediaOutputIndicatorWorker.java b/src/com/android/settings/media/MediaOutputIndicatorWorker.java index adee0557be9..2d10fd3f43e 100644 --- a/src/com/android/settings/media/MediaOutputIndicatorWorker.java +++ b/src/com/android/settings/media/MediaOutputIndicatorWorker.java @@ -16,26 +16,18 @@ package com.android.settings.media; -import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.net.Uri; import android.util.Log; -import com.android.internal.util.CollectionUtils; -import com.android.settings.R; import com.android.settings.bluetooth.Utils; import com.android.settings.slices.SliceBackgroundWorker; -import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; -import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; /** * Listener for background change from {@code BluetoothCallback} to update media output indicator. @@ -45,7 +37,6 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements private static final String TAG = "MediaOutputIndicatorWorker"; private LocalBluetoothManager mLocalBluetoothManager; - private LocalBluetoothProfileManager mProfileManager; public MediaOutputIndicatorWorker(Context context, Uri uri) { super(context, uri); @@ -53,12 +44,11 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements @Override protected void onSlicePinned() { - LocalBluetoothManager mLocalBluetoothManager = Utils.getLocalBtManager(getContext()); + mLocalBluetoothManager = Utils.getLocalBtManager(getContext()); if (mLocalBluetoothManager == null) { Log.e(TAG, "Bluetooth is not supported on this device"); return; } - mProfileManager = mLocalBluetoothManager.getProfileManager(); mLocalBluetoothManager.getEventManager().registerCallback(this); } @@ -74,7 +64,6 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements @Override public void close() throws IOException { mLocalBluetoothManager = null; - mProfileManager = null; } @Override @@ -89,73 +78,4 @@ public class MediaOutputIndicatorWorker extends SliceBackgroundWorker implements notifySliceChange(); } } - - /** - * To decide Slice's visibility. - * - * @return true if device is connected or previously connected, false for other cases. - */ - public boolean isVisible() { - return !CollectionUtils.isEmpty(getConnectableA2dpDevices()) - || !CollectionUtils.isEmpty(getConnectableHearingAidDevices()); - } - - private List getConnectableA2dpDevices() { - // get A2dp devices on all states - // (STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING) - final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); - if (a2dpProfile == null) { - return new ArrayList<>(); - } - return a2dpProfile.getConnectableDevices(); - } - - private List getConnectableHearingAidDevices() { - // get hearing aid profile devices on all states - // (STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING) - final HearingAidProfile hapProfile = mProfileManager.getHearingAidProfile(); - if (hapProfile == null) { - return new ArrayList<>(); - } - - return hapProfile.getConnectableDevices(); - } - - /** - * Get active devices name. - * - * @return active Bluetooth device alias, or default summary if no active device. - */ - public CharSequence findActiveDeviceName() { - // Return Hearing Aid device name if it is active - BluetoothDevice activeDevice = findActiveHearingAidDevice(); - if (activeDevice != null) { - return activeDevice.getAliasName(); - } - // Return A2DP device name if it is active - final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); - if (a2dpProfile != null) { - activeDevice = a2dpProfile.getActiveDevice(); - if (activeDevice != null) { - return activeDevice.getAliasName(); - } - } - // No active device, return default summary - return getContext().getText(R.string.media_output_default_summary); - } - - private BluetoothDevice findActiveHearingAidDevice() { - final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); - if (hearingAidProfile == null) { - return null; - } - - final List activeDevices = hearingAidProfile.getActiveDevices(); - for (BluetoothDevice btDevice : activeDevices) { - if (btDevice != null) { - return btDevice; - } - } - return null; - } } diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java index c5519601ae7..b39f77ed732 100644 --- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java +++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorSliceTest.java @@ -17,35 +17,29 @@ package com.android.settings.media; -import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI; - import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; import android.content.Context; -import android.content.Intent; import androidx.slice.Slice; -import androidx.slice.SliceItem; import androidx.slice.SliceMetadata; import androidx.slice.SliceProvider; -import androidx.slice.core.SliceAction; import androidx.slice.widget.SliceLiveData; import com.android.settings.R; -import com.android.settings.testutils.shadow.ShadowBluetoothAdapter; -import com.android.settingslib.media.LocalMediaManager; -import com.android.settingslib.media.MediaDevice; -import com.android.settingslib.media.MediaOutputSliceConstants; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; +import com.android.settingslib.bluetooth.A2dpProfile; +import com.android.settingslib.bluetooth.HearingAidProfile; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -53,59 +47,108 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadow.api.Shadow; import java.util.ArrayList; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowBluetoothAdapter.class}) -@Ignore("b/129292771") +@Config(shadows = {ShadowBluetoothUtils.class}) public class MediaOutputIndicatorSliceTest { - private static final String TEST_DEVICE_NAME = "test_device_name"; - private static final int TEST_DEVICE_1_ICON = - com.android.internal.R.drawable.ic_bt_headphones_a2dp; + private static final String TEST_A2DP_DEVICE_NAME = "Test_A2DP_BT_Device_NAME"; + private static final String TEST_HAP_DEVICE_NAME = "Test_HAP_BT_Device_NAME"; + private static final String TEST_A2DP_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; + private static final String TEST_HAP_DEVICE_ADDRESS = "00:B2:B2:B2:B2:B2"; @Mock - private LocalMediaManager mLocalMediaManager; - - private final List mDevices = new ArrayList<>(); + private A2dpProfile mA2dpProfile; + @Mock + private HearingAidProfile mHearingAidProfile; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private LocalBluetoothProfileManager mLocalBluetoothProfileManager; + private BluetoothAdapter mBluetoothAdapter; + private BluetoothDevice mA2dpDevice; + private BluetoothDevice mHapDevice; + private BluetoothManager mBluetoothManager; private Context mContext; + private List mDevicesList; private MediaOutputIndicatorSlice mMediaOutputIndicatorSlice; - private MediaOutputIndicatorWorker mMediaOutputIndicatorWorker; - private ShadowBluetoothAdapter mShadowBluetoothAdapter; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); - // Set-up specs for SliceMetadata. SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); + // Setup Bluetooth environment + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + mBluetoothManager = new BluetoothManager(mContext); + mBluetoothAdapter = mBluetoothManager.getAdapter(); + when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); + when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); + + // Setup A2dp device + mA2dpDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_A2DP_DEVICE_ADDRESS)); + when(mA2dpDevice.getName()).thenReturn(TEST_A2DP_DEVICE_NAME); + when(mA2dpDevice.isConnected()).thenReturn(true); + // Setup HearingAid device + mHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_HAP_DEVICE_ADDRESS)); + when(mHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME); + when(mHapDevice.isConnected()).thenReturn(true); + mMediaOutputIndicatorSlice = new MediaOutputIndicatorSlice(mContext); - mMediaOutputIndicatorWorker = spy(new MediaOutputIndicatorWorker( - mContext, MEDIA_OUTPUT_INDICATOR_SLICE_URI)); - mMediaOutputIndicatorSlice.mWorker = mMediaOutputIndicatorWorker; + mDevicesList = new ArrayList<>(); } @Test - public void getSlice_invisible_returnNull() { - when(mMediaOutputIndicatorWorker.isVisible()).thenReturn(false); + public void getSlice_noConnectableDevice_returnNull() { + mDevicesList.clear(); + when(mA2dpProfile.getConnectableDevices()).thenReturn(mDevicesList); assertThat(mMediaOutputIndicatorSlice.getSlice()).isNull(); } @Test - public void getSlice_withActiveDevice_checkContent() { - when(mMediaOutputIndicatorWorker.isVisible()).thenReturn(true); - when(mMediaOutputIndicatorWorker.findActiveDeviceName()).thenReturn(TEST_DEVICE_NAME); + public void getSlice_noActiveDevice_verifyDefaultName() { + mDevicesList.add(mA2dpDevice); + when(mA2dpProfile.getConnectableDevices()).thenReturn(mDevicesList); + when(mA2dpProfile.getActiveDevice()).thenReturn(null); + + // Verify slice title and subtitle final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice(); final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice); - // Verify slice title and subtitle assertThat(metadata.getTitle()).isEqualTo(mContext.getText(R.string.media_output_title)); - assertThat(metadata.getSubtitle()).isEqualTo(TEST_DEVICE_NAME); + assertThat(metadata.getSubtitle()).isEqualTo(mContext.getText( + R.string.media_output_default_summary)); + } + + @Test + public void getSlice_A2dpDeviceActive_verifyName() { + mDevicesList.add(mA2dpDevice); + when(mA2dpProfile.getConnectableDevices()).thenReturn(mDevicesList); + when(mA2dpProfile.getActiveDevice()).thenReturn(mA2dpDevice); + + final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice(); + final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice); + assertThat(metadata.getTitle()).isEqualTo(mContext.getText(R.string.media_output_title)); + assertThat(metadata.getSubtitle()).isEqualTo(TEST_A2DP_DEVICE_NAME); + } + + @Test + public void getSlice_HADeviceActive_verifyName() { + mDevicesList.add(mHapDevice); + when(mHearingAidProfile.getConnectableDevices()).thenReturn(mDevicesList); + when(mHearingAidProfile.getActiveDevices()).thenReturn(mDevicesList); + + // Verify slice title and subtitle + final Slice mediaSlice = mMediaOutputIndicatorSlice.getSlice(); + final SliceMetadata metadata = SliceMetadata.from(mContext, mediaSlice); + assertThat(metadata.getTitle()).isEqualTo(mContext.getText(R.string.media_output_title)); + assertThat(metadata.getSubtitle()).isEqualTo(TEST_HAP_DEVICE_NAME); } } diff --git a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java index ed93258346b..3671d819f83 100644 --- a/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java +++ b/tests/robotests/src/com/android/settings/media/MediaOutputIndicatorWorkerTest.java @@ -16,28 +16,18 @@ package com.android.settings.media; -import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothManager; import android.content.Context; import android.net.Uri; -import com.android.settings.R; -import com.android.settings.bluetooth.Utils; import com.android.settings.testutils.shadow.ShadowBluetoothUtils; -import com.android.settingslib.bluetooth.A2dpProfile; import com.android.settingslib.bluetooth.BluetoothEventManager; -import com.android.settingslib.bluetooth.HearingAidProfile; import com.android.settingslib.bluetooth.LocalBluetoothManager; -import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -45,119 +35,38 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowBluetoothDevice; - -import java.util.ArrayList; -import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowBluetoothUtils.class, - ShadowBluetoothDevice.class}) -@Ignore("b/129292771") +@Config(shadows = {ShadowBluetoothUtils.class}) public class MediaOutputIndicatorWorkerTest { - - private static final String TEST_A2DP_DEVICE_NAME = "Test_A2DP_BT_Device_NAME"; - private static final String TEST_HAP_DEVICE_NAME = "Test_HAP_BT_Device_NAME"; - private static final String TEST_A2DP_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; - private static final String TEST_HAP_DEVICE_ADDRESS = "00:B2:B2:B2:B2:B2"; private static final Uri URI = Uri.parse("content://com.android.settings.slices/test"); - @Mock - private A2dpProfile mA2dpProfile; - @Mock - private HearingAidProfile mHearingAidProfile; - @Mock - private LocalBluetoothManager mLocalManager; @Mock private BluetoothEventManager mBluetoothEventManager; @Mock - private LocalBluetoothProfileManager mLocalBluetoothProfileManager; - - private BluetoothAdapter mBluetoothAdapter; - private BluetoothDevice mA2dpDevice; - private BluetoothDevice mHapDevice; - private BluetoothManager mBluetoothManager; - private Context mContext; - private List mDevicesList; private LocalBluetoothManager mLocalBluetoothManager; + private Context mContext; private MediaOutputIndicatorWorker mMediaDeviceUpdateWorker; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); - ShadowBluetoothUtils.sLocalBluetoothManager = mLocalManager; - mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); - when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager); - when(mLocalBluetoothProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); - when(mLocalBluetoothProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile); - mBluetoothManager = new BluetoothManager(mContext); - mBluetoothAdapter = mBluetoothManager.getAdapter(); - - // Setup A2dp device - mA2dpDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_A2DP_DEVICE_ADDRESS)); - when(mA2dpDevice.getName()).thenReturn(TEST_A2DP_DEVICE_NAME); - when(mA2dpDevice.isConnected()).thenReturn(true); - // Setup HearingAid device - mHapDevice = spy(mBluetoothAdapter.getRemoteDevice(TEST_HAP_DEVICE_ADDRESS)); - when(mHapDevice.getName()).thenReturn(TEST_HAP_DEVICE_NAME); - when(mHapDevice.isConnected()).thenReturn(true); - mMediaDeviceUpdateWorker = new MediaOutputIndicatorWorker(mContext, URI); - mDevicesList = new ArrayList<>(); } @Test - public void isVisible_noConnectableDevice_returnFalse() { - mDevicesList.clear(); - when(mA2dpProfile.getConnectableDevices()).thenReturn(mDevicesList); - - assertThat(mMediaDeviceUpdateWorker.isVisible()).isFalse(); + public void onSlicePinned_registerCallback() { + mMediaDeviceUpdateWorker.onSlicePinned(); + verify(mBluetoothEventManager).registerCallback(mMediaDeviceUpdateWorker); } @Test - public void isVisible_withConnectableA2dpDevice_returnTrue() { - mDevicesList.clear(); - mDevicesList.add(mA2dpDevice); - when(mHearingAidProfile.getConnectableDevices()).thenReturn(mDevicesList); - - assertThat(mMediaDeviceUpdateWorker.isVisible()).isTrue(); - } - - @Test - public void isVisible_withConnectableHADevice_returnTrue() { - mDevicesList.clear(); - mDevicesList.add(mHapDevice); - when(mA2dpProfile.getConnectableDevices()).thenReturn(mDevicesList); - - assertThat(mMediaDeviceUpdateWorker.isVisible()).isTrue(); - } - - @Test - public void findActiveDeviceName_A2dpDeviceActive_verifyName() { - when(mA2dpProfile.getActiveDevice()).thenReturn(mA2dpDevice); - - assertThat(mMediaDeviceUpdateWorker.findActiveDeviceName()) - .isEqualTo(mA2dpDevice.getAliasName()); - } - - @Test - public void findActiveDeviceName_HADeviceActive_verifyName() { - mDevicesList.add(mHapDevice); - when(mHearingAidProfile.getActiveDevices()).thenReturn(mDevicesList); - - assertThat(mMediaDeviceUpdateWorker.findActiveDeviceName()) - .isEqualTo(mHapDevice.getAliasName()); - } - - @Test - public void findActiveDeviceName_noActiveDevice_verifyDefaultName() { - when(mA2dpProfile.getActiveDevice()).thenReturn(null); - mDevicesList.clear(); - when(mHearingAidProfile.getActiveDevices()).thenReturn(mDevicesList); - - assertThat(mMediaDeviceUpdateWorker.findActiveDeviceName()) - .isEqualTo(mContext.getText(R.string.media_output_default_summary)); + public void onSliceUnpinned_unRegisterCallback() { + mMediaDeviceUpdateWorker.onSlicePinned(); + mMediaDeviceUpdateWorker.onSliceUnpinned(); + verify(mBluetoothEventManager).unregisterCallback(mMediaDeviceUpdateWorker); } } From ca974ae0f4ed5b4ce9391a53c3a1e16b8f99acc8 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Wed, 27 Mar 2019 11:09:11 -0400 Subject: [PATCH 05/43] Launch correct tone picker for audio attributes Test: robotests Fixes: 129353516 Change-Id: Iacbf5008f186e59377e534da747544d33a20f81f --- res/xml/channel_notification_settings.xml | 3 +- .../SoundPreferenceController.java | 14 ++++ .../SoundPreferenceControllerTest.java | 66 +++++++++++++++++++ 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/res/xml/channel_notification_settings.xml b/res/xml/channel_notification_settings.xml index 3158819df13..14e0366af31 100644 --- a/res/xml/channel_notification_settings.xml +++ b/res/xml/channel_notification_settings.xml @@ -63,8 +63,7 @@ android:dialogTitle="@string/notification_channel_sound_title" android:order="11" android:showSilent="true" - android:showDefault="true" - android:ringtoneType="notification" /> + android:showDefault="true"/> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(pref, times(1)).onPrepareRingtonePickerIntent(intentArgumentCaptor.capture()); + assertEquals(RingtoneManager.TYPE_ALARM, + intentArgumentCaptor.getValue().getIntExtra( + RingtoneManager.EXTRA_RINGTONE_TYPE, 0)); + } + + @Test + public void testOnPreferenceTreeClick_ringtoneSound() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); + channel.setSound(null, new AudioAttributes.Builder().setUsage( + AudioAttributes.USAGE_NOTIFICATION_RINGTONE).build()); + mController.onResume(appRow, channel, null, null); + + AttributeSet attributeSet = Robolectric.buildAttributeSet().build(); + NotificationSoundPreference pref = + spy(new NotificationSoundPreference(mContext, attributeSet)); + pref.setKey(mController.getPreferenceKey()); + mController.handlePreferenceTreeClick(pref); + + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(pref, times(1)).onPrepareRingtonePickerIntent(intentArgumentCaptor.capture()); + assertEquals(RingtoneManager.TYPE_RINGTONE, + intentArgumentCaptor.getValue().getIntExtra( + RingtoneManager.EXTRA_RINGTONE_TYPE, 0)); + } + + @Test + public void testOnPreferenceTreeClick_otherSound() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = new NotificationChannel("", "", IMPORTANCE_HIGH); + channel.setSound(null, new AudioAttributes.Builder().setUsage( + AudioAttributes.USAGE_UNKNOWN).build()); + mController.onResume(appRow, channel, null, null); + + AttributeSet attributeSet = Robolectric.buildAttributeSet().build(); + NotificationSoundPreference pref = + spy(new NotificationSoundPreference(mContext, attributeSet)); + pref.setKey(mController.getPreferenceKey()); + mController.handlePreferenceTreeClick(pref); + + ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(pref, times(1)).onPrepareRingtonePickerIntent(intentArgumentCaptor.capture()); + assertEquals(RingtoneManager.TYPE_NOTIFICATION, + intentArgumentCaptor.getValue().getIntExtra( + RingtoneManager.EXTRA_RINGTONE_TYPE, 0)); + } + @Test public void testOnActivityResult() { NotificationSoundPreference pref = mock(NotificationSoundPreference.class); From 3c2c658791ddef6cc096fdd2e898f06df07813c3 Mon Sep 17 00:00:00 2001 From: Quang Luong Date: Wed, 27 Mar 2019 14:39:19 -0700 Subject: [PATCH 06/43] Added null pointer check for mWifiConfig.macRandomizationSetting Without the null pointer check, a passpoint network in the saved networks page will throw a NullPointerException since there is no config associated with it. Bug: 129422562 Test: manual Change-Id: I4a085a16c4181b7bdfe46c279e579d34ba314d81 --- .../settings/wifi/details/WifiDetailPreferenceController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java index 955525dd859..22b9376152a 100644 --- a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java +++ b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java @@ -663,7 +663,8 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController } // return randomized MAC address - if (mWifiConfig.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) { + if (mWifiConfig != null && + mWifiConfig.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT) { return mWifiConfig.getRandomizedMacAddress().toString(); } From b0a01f2172721d8fe5d57c6617c67e45332cd5bd Mon Sep 17 00:00:00 2001 From: Fan Zhang Date: Wed, 27 Mar 2019 15:24:40 -0700 Subject: [PATCH 07/43] Update "Time spent in app" to "Screen time" Bug: 129266977 Test: manual Change-Id: I2a26dfb8c36ba00a73c69b5e7a7333bf20a83224 --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 11281c285c7..16c01a3de3a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -8830,7 +8830,7 @@ App usage preferences - Time spent in app + Screen time Usage access allows an app to track what other apps you\u2019re using and how often, as well as your carrier, language settings, and other details. From d832dcc7c58e46c0f656bc36ad15aaab28a8d257 Mon Sep 17 00:00:00 2001 From: Quang Luong Date: Wed, 27 Mar 2019 15:28:34 -0700 Subject: [PATCH 08/43] Disable Mac randomization setting for Passpoint networks Mac randomization setting should be greyed out in the network details page for Passpoint networks. Bug: 129279500 Test: atest WifiPrivacyPreferenceControllerTest Change-Id: I4fc89ddbca839c4859f11f8fad212b3e0e4aa7ce --- .../wifi/details/WifiNetworkDetailsFragment.java | 2 ++ .../details/WifiPrivacyPreferenceController.java | 7 ++++++- .../WifiPrivacyPreferenceControllerTest.java | 16 ++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java index ee4cc2983c7..10d1d48f4a2 100644 --- a/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java +++ b/src/com/android/settings/wifi/details/WifiNetworkDetailsFragment.java @@ -140,6 +140,8 @@ public class WifiNetworkDetailsFragment extends DashboardFragment { context); privacyController.setWifiConfiguration(mAccessPoint.getConfig()); privacyController.setIsEphemeral(mAccessPoint.isEphemeral()); + privacyController.setIsPasspoint( + mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()); controllers.add(privacyController); return controllers; diff --git a/src/com/android/settings/wifi/details/WifiPrivacyPreferenceController.java b/src/com/android/settings/wifi/details/WifiPrivacyPreferenceController.java index a549e21ac27..7bec4119f8d 100644 --- a/src/com/android/settings/wifi/details/WifiPrivacyPreferenceController.java +++ b/src/com/android/settings/wifi/details/WifiPrivacyPreferenceController.java @@ -41,6 +41,7 @@ public class WifiPrivacyPreferenceController extends BasePreferenceController im private WifiConfiguration mWifiConfiguration; private WifiManager mWifiManager; private boolean mIsEphemeral = false; + private boolean mIsPasspoint = false; public WifiPrivacyPreferenceController(Context context) { super(context, KEY_WIFI_PRIVACY); @@ -56,6 +57,10 @@ public class WifiPrivacyPreferenceController extends BasePreferenceController im mIsEphemeral = isEphemeral; } + public void setIsPasspoint(boolean isPasspoint) { + mIsPasspoint = isPasspoint; + } + @Override public int getAvailabilityStatus() { return mContext.getResources().getBoolean( @@ -71,7 +76,7 @@ public class WifiPrivacyPreferenceController extends BasePreferenceController im updateSummary(dropDownPreference, randomizationLevel); // Makes preference not selectable, when this is a ephemeral network. - if (mIsEphemeral) { + if (mIsEphemeral || mIsPasspoint) { preference.setSelectable(false); dropDownPreference.setSummary(R.string.wifi_privacy_settings_ephemeral_summary); } diff --git a/tests/robotests/src/com/android/settings/wifi/details/WifiPrivacyPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/wifi/details/WifiPrivacyPreferenceControllerTest.java index a1af8bf272c..2e588b54a69 100644 --- a/tests/robotests/src/com/android/settings/wifi/details/WifiPrivacyPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/wifi/details/WifiPrivacyPreferenceControllerTest.java @@ -109,4 +109,20 @@ public class WifiPrivacyPreferenceControllerTest { assertThat(mDropDownPreference.isSelectable()).isFalse(); } + + @Test + public void testUpdateState_isNotPasspointNetwork_shouldBeSelectable() { + mPreferenceController.setIsPasspoint(false); + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.isSelectable()).isTrue(); + } + + @Test + public void testUpdateState_isPasspointNetwork_shouldNotSelectable() { + mPreferenceController.setIsPasspoint(true); + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.isSelectable()).isFalse(); + } } From 682763974d02a5a5f41df11361a45381ddbf9543 Mon Sep 17 00:00:00 2001 From: Raff Tsai Date: Wed, 27 Mar 2019 16:50:55 +0800 Subject: [PATCH 09/43] Log RecyclerView click position Test: rebuild, robolectric Fixes: 127881281 Change-Id: Idddbbf9c1159e0e5b280178e98f9258cbd7864f9 --- .../ContextualCardFeatureProvider.java | 2 +- .../ContextualCardFeatureProviderImpl.java | 6 +++- .../slices/SliceContextualCardRenderer.java | 5 +-- .../SliceDeferredSetupCardRendererHelper.java | 2 +- .../slices/SliceFullCardRendererHelper.java | 35 ++++++------------- .../slices/SliceHalfCardRendererHelper.java | 2 +- .../SliceFullCardRendererHelperTest.java | 8 ++--- 7 files changed, 24 insertions(+), 36 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java index b1e9d33e682..c296c7a6d20 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProvider.java @@ -32,5 +32,5 @@ public interface ContextualCardFeatureProvider { List hiddenCards); /** When user clicks toggle/title area of a contextual card. */ - void logContextualCardClick(ContextualCard card, int row, int tapTarget); + void logContextualCardClick(ContextualCard card, int sliceRow, int tapTarget, int uiPosition); } diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java index ce7777a5e5b..793134f9bc0 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardFeatureProviderImpl.java @@ -63,6 +63,9 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP // contextual card tap target private static final String EXTRA_CONTEXTUALCARD_TAP_TARGET = "target"; + // contextual card ui position + private static final String EXTRA_CONTEXTUALCARD_UI_POSTITION = "ui_position"; + // contextual homepage display latency private static final String EXTRA_LATENCY = "latency"; @@ -122,7 +125,7 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP @Override public void logContextualCardClick(ContextualCard card, int row, - int actionType) { + int actionType, int uiPosition) { final Intent intent = new Intent(); intent.putExtra(EXTRA_CONTEXTUALCARD_ACTION_TYPE, CONTEXTUAL_CARD_CLICK); intent.putExtra(EXTRA_CONTEXTUALCARD_NAME, card.getName()); @@ -130,6 +133,7 @@ public class ContextualCardFeatureProviderImpl implements ContextualCardFeatureP intent.putExtra(EXTRA_CONTEXTUALCARD_SCORE, card.getRankingScore()); intent.putExtra(EXTRA_CONTEXTUALCARD_ROW, row); intent.putExtra(EXTRA_CONTEXTUALCARD_TAP_TARGET, actionTypeToTapTarget(actionType)); + intent.putExtra(EXTRA_CONTEXTUALCARD_UI_POSTITION, uiPosition); sendBroadcast(intent); } diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java index 006734f2f8d..3ecfcaecdae 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRenderer.java @@ -64,7 +64,6 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, Life private final Context mContext; private final LifecycleOwner mLifecycleOwner; private final ControllerRendererPool mControllerRendererPool; - private final Set mCardSet; private final SliceDeferredSetupCardRendererHelper mDeferredSetupCardHelper; private final SliceFullCardRendererHelper mFullCardHelper; private final SliceHalfCardRendererHelper mHalfCardHelper; @@ -75,7 +74,6 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, Life mLifecycleOwner = lifecycleOwner; mSliceLiveDataMap = new ArrayMap<>(); mControllerRendererPool = controllerRendererPool; - mCardSet = new ArraySet<>(); mFlippedCardSet = new ArraySet<>(); mLifecycleOwner.getLifecycle().addObserver(this); mFullCardHelper = new SliceFullCardRendererHelper(context); @@ -110,7 +108,6 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, Life sliceLiveData = SliceLiveData.fromUri(mContext, uri); mSliceLiveDataMap.put(uri, sliceLiveData); } - mCardSet.add(card); sliceLiveData.removeObservers(mLifecycleOwner); sliceLiveData.observe(mLifecycleOwner, slice -> { @@ -129,7 +126,7 @@ public class SliceContextualCardRenderer implements ContextualCardRenderer, Life mHalfCardHelper.bindView(holder, card, slice); break; default: - mFullCardHelper.bindView(holder, card, slice, mCardSet); + mFullCardHelper.bindView(holder, card, slice); } }); diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceDeferredSetupCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceDeferredSetupCardRendererHelper.java index d0d51e74cc7..630839c1340 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceDeferredSetupCardRendererHelper.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceDeferredSetupCardRendererHelper.java @@ -68,7 +68,7 @@ class SliceDeferredSetupCardRendererHelper { final ContextualCardFeatureProvider contextualCardFeatureProvider = FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext); contextualCardFeatureProvider.logContextualCardClick(card, 0 /* row */, - EventInfo.ACTION_TYPE_CONTENT); + EventInfo.ACTION_TYPE_CONTENT, view.getAdapterPosition()); }); } diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java index ef0a67d0e20..1e1bde4981d 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelper.java @@ -17,6 +17,7 @@ package com.android.settings.homepage.contextualcards.slices; import android.content.Context; +import android.util.Log; import android.view.View; import androidx.annotation.NonNull; @@ -36,13 +37,11 @@ import java.util.Set; /** * Card renderer helper for {@link ContextualCard} built as slice full card. */ -class SliceFullCardRendererHelper implements SliceView.OnSliceActionListener { +class SliceFullCardRendererHelper { private static final String TAG = "SliceFCRendererHelper"; private final Context mContext; - private Set mCardSet; - SliceFullCardRendererHelper(Context context) { mContext = context; } @@ -51,17 +50,22 @@ class SliceFullCardRendererHelper implements SliceView.OnSliceActionListener { return new SliceViewHolder(view); } - void bindView(RecyclerView.ViewHolder holder, ContextualCard card, Slice slice, - Set cardSet) { + void bindView(RecyclerView.ViewHolder holder, ContextualCard card, Slice slice) { final SliceViewHolder cardHolder = (SliceViewHolder) holder; cardHolder.sliceView.setScrollable(false); cardHolder.sliceView.setTag(card.getSliceUri()); //TODO(b/114009676): We will soon have a field to decide what slice mode we should set. cardHolder.sliceView.setMode(SliceView.MODE_LARGE); cardHolder.sliceView.setSlice(slice); - mCardSet = cardSet; // Set this listener so we can log the interaction users make on the slice - cardHolder.sliceView.setOnSliceActionListener(this); + cardHolder.sliceView.setOnSliceActionListener( + (eventInfo, sliceItem) -> { + final ContextualCardFeatureProvider contextualCardFeatureProvider = + FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider( + mContext); + contextualCardFeatureProvider.logContextualCardClick(card, eventInfo.rowIndex, + eventInfo.actionType, cardHolder.getAdapterPosition()); + }); // Customize slice view for Settings cardHolder.sliceView.showTitleItems(true); @@ -71,23 +75,6 @@ class SliceFullCardRendererHelper implements SliceView.OnSliceActionListener { } } - @Override - public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) { - // sliceItem.getSlice().getUri() is like - // content://android.settings.slices/action/wifi/_gen/0/_gen/0 - // contextualCard.getSliceUri() is prefix of sliceItem.getSlice().getUri() - final ContextualCardFeatureProvider contextualCardFeatureProvider = - FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext); - for (ContextualCard card : mCardSet) { - if (sliceItem.getSlice().getUri().toString().startsWith( - card.getSliceUri().toString())) { - contextualCardFeatureProvider.logContextualCardClick(card, eventInfo.rowIndex, - eventInfo.actionType); - break; - } - } - } - static class SliceViewHolder extends RecyclerView.ViewHolder { public final SliceView sliceView; diff --git a/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java index 24d654d885c..6bb22087925 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SliceHalfCardRendererHelper.java @@ -66,7 +66,7 @@ class SliceHalfCardRendererHelper { final ContextualCardFeatureProvider contextualCardFeatureProvider = FeatureFactory.getFactory(mContext).getContextualCardFeatureProvider(mContext); contextualCardFeatureProvider.logContextualCardClick(card, 0 /* row */, - EventInfo.ACTION_TYPE_CONTENT); + EventInfo.ACTION_TYPE_CONTENT, view.getAdapterPosition()); }); } diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java index 88820096fce..395748bc0fb 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceFullCardRendererHelperTest.java @@ -78,7 +78,7 @@ public class SliceFullCardRendererHelperTest { public void bindView_shouldSetScrollableToFalse() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet()); + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice()); assertThat(((SliceViewHolder) viewHolder).sliceView.isScrollable()).isFalse(); } @@ -88,7 +88,7 @@ public class SliceFullCardRendererHelperTest { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); final ContextualCard card = buildContextualCard(); - mHelper.bindView(viewHolder, card, buildSlice(), Collections.emptySet()); + mHelper.bindView(viewHolder, card, buildSlice()); assertThat(((SliceViewHolder) viewHolder).sliceView.getTag()).isEqualTo(card.getSliceUri()); } @@ -97,7 +97,7 @@ public class SliceFullCardRendererHelperTest { public void bindView_shouldSetModeToLarge() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet()); + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice()); assertThat(((SliceViewHolder) viewHolder).sliceView.getMode()).isEqualTo( SliceView.MODE_LARGE); @@ -107,7 +107,7 @@ public class SliceFullCardRendererHelperTest { public void bindView_shouldSetSlice() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - mHelper.bindView(viewHolder, buildContextualCard(), buildSlice(), Collections.emptySet()); + mHelper.bindView(viewHolder, buildContextualCard(), buildSlice()); assertThat(((SliceViewHolder) viewHolder).sliceView.getSlice().getUri()).isEqualTo( TEST_SLICE_URI); From 6f096fd70692ba3563e3d714a2c4bbcf861518a8 Mon Sep 17 00:00:00 2001 From: Qingxi Li Date: Wed, 27 Mar 2019 14:40:30 -0700 Subject: [PATCH 10/43] [DO NOT MERGE] Fix the typo of the error message of eSIM reset Bug: 129085179 Test: N/A Change-Id: Iad8fdc7368a4d91aff9eb3035bfe91592d6bce1b --- res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 44e26c33dba..a442ddbed3a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3403,9 +3403,9 @@ Network settings have been reset - Cant\u2019t reset eSIMs + Can\u2019t reset eSIMs - The eSIMs can\u2019tt be reset due to an error. + The eSIMs can\u2019t be reset due to an error. From 14847a27ebdf76f0c69ff3f7bfbba3f8a3d7f17b Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Wed, 27 Mar 2019 17:24:46 -0700 Subject: [PATCH 11/43] Make "Advanced" show in Apps & notifications screen Reducing the number of initialExpandedChildrenCount to 3 will make everything under "Default apps" be under "Advanced". Fixes: 129284718 Test: visual, rebuild Change-Id: I540385c514f6e8bf005e90e9ed4a9f93a2e983d5 --- res/xml/app_and_notification.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/xml/app_and_notification.xml b/res/xml/app_and_notification.xml index 9dd5feaa1ce..dfcefc0bb87 100644 --- a/res/xml/app_and_notification.xml +++ b/res/xml/app_and_notification.xml @@ -20,7 +20,7 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:key="apps_and_notification_screen" android:title="@string/app_and_notification_dashboard_title" - settings:initialExpandedChildrenCount="8"> + settings:initialExpandedChildrenCount="3"> Date: Tue, 26 Mar 2019 15:14:46 -0700 Subject: [PATCH 12/43] Update battery saver schedule to show warning If a user tries to enable a battery saver schedule we want to show them the battery saver warning if they haven't seen it. This CL does that. Test: robotests pass, manual verification Bug: 128924009 Change-Id: I683279716ac7700043b1a01f88bdf2ae716eeece --- ...rySaverScheduleRadioButtonsController.java | 48 ++++++++++++------- ...verScheduleRadioButtonsControllerTest.java | 10 ++++ 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsController.java b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsController.java index 796df469a6a..57bae456e0a 100644 --- a/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsController.java +++ b/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsController.java @@ -20,6 +20,8 @@ import android.content.Context; import android.os.PowerManager; import android.provider.Settings; import android.provider.Settings.Global; +import android.text.TextUtils; +import com.android.settingslib.fuelgauge.BatterySaverUtils; /** * Responds to user actions in the Settings > Battery > Set a Schedule Screen @@ -66,24 +68,34 @@ public class BatterySaverScheduleRadioButtonsController { public boolean setDefaultKey(String key) { final ContentResolver resolver = mContext.getContentResolver(); - switch(key) { - case KEY_NO_SCHEDULE: - Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, - PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE); - Settings.Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0); - break; - case KEY_PERCENTAGE: - Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, - PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE); - Settings.Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 5); - break; - case KEY_ROUTINE: - Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, - PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC); - break; - default: - throw new IllegalStateException( - "Not a valid key for " + this.getClass().getSimpleName()); + + int mode = PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE; + int triggerLevel = 0; + if (!TextUtils.equals(key, KEY_NO_SCHEDULE) + && BatterySaverUtils.maybeShowBatterySaverConfirmation( + mContext, true /* confirmOnly */)) { + return true; + } else { + switch (key) { + case KEY_NO_SCHEDULE: + break; + case KEY_PERCENTAGE: + triggerLevel = 5; + break; + case KEY_ROUTINE: + mode = PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC; + break; + default: + throw new IllegalStateException( + "Not a valid key for " + this.getClass().getSimpleName()); + } + } + + // Trigger level is intentionally left alone when going between dynamic and percentage modes + // so that a users percentage based schedule is preserved when they toggle between the two. + Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE, mode); + if (mode != PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC) { + Settings.Global.putInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, triggerLevel); } mSeekBarController.updateSeekBar(); return true; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsControllerTest.java index 6012dbb4dc8..8654a4e5a7b 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterysaver/BatterySaverScheduleRadioButtonsControllerTest.java @@ -7,6 +7,7 @@ import android.content.Context; import android.os.PowerManager; import android.provider.Settings; import android.provider.Settings.Global; +import android.provider.Settings.Secure; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -54,4 +55,13 @@ public class BatterySaverScheduleRadioButtonsControllerTest { assertThat(mController.getDefaultKey()) .isEqualTo(BatterySaverScheduleRadioButtonsController.KEY_NO_SCHEDULE); } + + @Test + public void setDefaultKey_any_defaultsToNoScheduleIfWarningNotSeen() { + Secure.putString( + mContext.getContentResolver(), Secure.LOW_POWER_WARNING_ACKNOWLEDGED, "null"); + mController.setDefaultKey(BatterySaverScheduleRadioButtonsController.KEY_ROUTINE); + assertThat(mController.getDefaultKey()) + .isEqualTo(BatterySaverScheduleRadioButtonsController.KEY_NO_SCHEDULE); + } } From a05e6f8182db42b028a1c2d1d6779b3c268958cd Mon Sep 17 00:00:00 2001 From: Malcolm Chen Date: Wed, 27 Mar 2019 17:45:29 -0700 Subject: [PATCH 13/43] Correct name of PRIMARY_SUBSCRIPTION_LIST_CHANGED. Correct name of PRIMARY_SUBSCRIPTION_LIST_CHANGED in manifest. Also, make sure SimSelectNotification only listens to that intent. Bug: 128645056 Test: manual Change-Id: I008e5346ff1e015d1aa57ca31844426e33bd99b8 --- AndroidManifest.xml | 2 +- src/com/android/settings/sim/SimSelectNotification.java | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 9f6cdb50487..95fe918b6d0 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2566,7 +2566,7 @@ - + diff --git a/src/com/android/settings/sim/SimSelectNotification.java b/src/com/android/settings/sim/SimSelectNotification.java index 4f3b9bc3f79..a1e942a8abe 100644 --- a/src/com/android/settings/sim/SimSelectNotification.java +++ b/src/com/android/settings/sim/SimSelectNotification.java @@ -30,6 +30,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; import com.android.settings.R; import com.android.settings.Settings.SimSettingsActivity; @@ -45,6 +46,9 @@ public class SimSelectNotification extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + if (!TelephonyManager.ACTION_PRIMARY_SUBSCRIPTION_LIST_CHANGED.equals(intent.getAction())) { + return; + } // Cancel any previous notifications cancelNotification(context); // Create a notification to tell the user that some defaults are missing From 194abe94d0999e9cd81095991c4c135c6ef15b42 Mon Sep 17 00:00:00 2001 From: Salvador Martinez Date: Wed, 27 Mar 2019 18:29:40 -0700 Subject: [PATCH 14/43] Update dark mode in accessiblity settings There was an entry point to dark mode that wasn't updated when the display settings one was changed to point to a new screen. This change makes it so that clicking dark mode in accessiblity also opens the same screen. Test: robotests pass Bug: 129406772 Change-Id: I0ad830638e04e5520cdf7de6927764fa6b68a179 --- res/xml/accessibility_settings.xml | 6 ++---- .../settings/accessibility/AccessibilitySettings.java | 2 +- .../settings/accessibility/AccessibilitySettingsTest.java | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/res/xml/accessibility_settings.xml b/res/xml/accessibility_settings.xml index da103c57bf4..4bcca239538 100644 --- a/res/xml/accessibility_settings.xml +++ b/res/xml/accessibility_settings.xml @@ -58,12 +58,10 @@ android:title="@string/screen_zoom_title" settings:searchable="false"/> - Date: Wed, 27 Mar 2019 14:27:50 -0700 Subject: [PATCH 15/43] Use explicit intent to broadcast grayscale state changed Query PackageManager for receivers that can handle the intent and send an explicit intent to these receivers. Change-Id: I06f500c20ae0a20d689cf0805b6a82247ea55e73 Fix: 118387886 Test: robotests --- .../GrayscaleConditionController.java | 20 ++++++++++++++----- .../GrayscaleConditionControllerTest.java | 13 +++++++++++- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java b/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java index 341e0612568..61b24df6cba 100644 --- a/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java +++ b/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java @@ -22,21 +22,27 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.hardware.display.ColorDisplayManager; import android.util.Log; +import androidx.annotation.VisibleForTesting; + import com.android.settings.R; import com.android.settings.homepage.contextualcards.ContextualCard; import java.net.URISyntaxException; +import java.util.List; import java.util.Objects; public class GrayscaleConditionController implements ConditionalCardController { static final int ID = Objects.hash("GrayscaleConditionController"); + @VisibleForTesting + static final String ACTION_GRAYSCALE_CHANGED = "android.settings.action.GRAYSCALE_CHANGED"; + private static final String TAG = "GrayscaleCondition"; - private static final String ACTION_GRAYSCALE_CHANGED = - "android.settings.action.GRAYSCALE_CHANGED"; private static final IntentFilter GRAYSCALE_CHANGED_FILTER = new IntentFilter( ACTION_GRAYSCALE_CHANGED); @@ -113,9 +119,13 @@ public class GrayscaleConditionController implements ConditionalCardController { } private void sendBroadcast() { - final Intent intent = new Intent(); - intent.setAction(ACTION_GRAYSCALE_CHANGED); - mAppContext.sendBroadcast(intent, Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS); + final PackageManager pm = mAppContext.getPackageManager(); + final Intent intent = new Intent(ACTION_GRAYSCALE_CHANGED); + final List receivers = pm.queryBroadcastReceivers(intent, 0 /* flags */); + for (ResolveInfo receiver : receivers) { + intent.setPackage(receiver.activityInfo.packageName); + mAppContext.sendBroadcast(intent); + } } public class Receiver extends BroadcastReceiver { diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java index 2fe4697f099..11e76b53295 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java @@ -25,6 +25,8 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.ResolveInfo; import android.hardware.display.ColorDisplayManager; import org.junit.Before; @@ -34,7 +36,9 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowPackageManager; @RunWith(RobolectricTestRunner.class) public class GrayscaleConditionControllerTest { @@ -45,6 +49,7 @@ public class GrayscaleConditionControllerTest { private ColorDisplayManager mColorDisplayManager; private Context mContext; private GrayscaleConditionController mController; + private ShadowPackageManager mPackageManager; @Before public void setUp() { @@ -53,6 +58,7 @@ public class GrayscaleConditionControllerTest { mColorDisplayManager = spy(mContext.getSystemService(ColorDisplayManager.class)); doReturn(mColorDisplayManager).when(mContext).getSystemService(ColorDisplayManager.class); mController = new GrayscaleConditionController(mContext, mConditionManager); + mPackageManager = Shadows.shadowOf(mContext.getPackageManager()); } @Test @@ -85,8 +91,13 @@ public class GrayscaleConditionControllerTest { @Test public void onActionClick_shouldSendBroadcast() { + final Intent intent = new Intent(GrayscaleConditionController.ACTION_GRAYSCALE_CHANGED); + final ResolveInfo info = new ResolveInfo(); + info.activityInfo = new ActivityInfo(); + mPackageManager.addResolveInfoForIntent(intent, info); + mController.onActionClick(); - verify(mContext).sendBroadcast(any(Intent.class), any(String.class)); + verify(mContext).sendBroadcast(any(Intent.class)); } } From eeea6676d4c8093ef9966b323e1cb94a85555096 Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Tue, 26 Mar 2019 21:16:55 +0800 Subject: [PATCH 16/43] Add implementation of homepage swipe to dismiss. - Only enable swipe for slice full/half card. - Add isPendingDismiss in ContextualCard to determine if we should show dismissal view. - Take out long press feature. Bug: 126214056 Test: robotests Change-Id: Ib03e605347b2f50d3c62fcd4f95875a21cc9ef1c --- .../contextualcards/ContextualCard.java | 14 ++ .../ContextualCardsAdapter.java | 9 +- .../slices/SliceContextualCardRenderer.java | 20 +-- .../slices/SwipeDismissalDelegate.java | 46 +++++- .../SliceContextualCardRendererTest.java | 39 +---- .../slices/SwipeDismissalDelegateTest.java | 145 ++++++++++++++++++ 6 files changed, 223 insertions(+), 50 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCard.java b/src/com/android/settings/homepage/contextualcards/ContextualCard.java index 7b8a0c395fe..ede12fb00c1 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCard.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCard.java @@ -71,6 +71,7 @@ public class ContextualCard { private final Drawable mIconDrawable; @LayoutRes private final int mViewType; + private final boolean mIsPendingDismiss; public String getName() { return mName; @@ -156,6 +157,10 @@ public class ContextualCard { return mViewType; } + public boolean isPendingDismiss() { + return mIsPendingDismiss; + } + public Builder mutate() { return mBuilder; } @@ -181,6 +186,7 @@ public class ContextualCard { mIconDrawable = builder.mIconDrawable; mIsLargeCard = builder.mIsLargeCard; mViewType = builder.mViewType; + mIsPendingDismiss = builder.mIsPendingDismiss; } ContextualCard(Cursor c) { @@ -226,6 +232,8 @@ public class ContextualCard { mBuilder.setIconDrawable(mIconDrawable); mViewType = getViewTypeByCardType(mCardType); mBuilder.setViewType(mViewType); + mIsPendingDismiss = false; + mBuilder.setIsPendingDismiss(mIsPendingDismiss); } @Override @@ -277,6 +285,7 @@ public class ContextualCard { private boolean mIsLargeCard; @LayoutRes private int mViewType; + private boolean mIsPendingDismiss; public Builder setName(String name) { mName = name; @@ -373,6 +382,11 @@ public class ContextualCard { return this; } + public Builder setIsPendingDismiss(boolean isPendingDismiss) { + mIsPendingDismiss = isPendingDismiss; + return this; + } + public ContextualCard build() { return new ContextualCard(this); } diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java b/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java index d6df380239a..7be0e8e72a5 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardsAdapter.java @@ -28,7 +28,7 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer; -import com.android.settings.homepage.contextualcards.slices.SwipeDismissalDelegate.DismissalItemTouchHelperListener; +import com.android.settings.homepage.contextualcards.slices.SwipeDismissalDelegate; import com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer; import java.util.ArrayList; @@ -36,7 +36,7 @@ import java.util.List; import java.util.Map; public class ContextualCardsAdapter extends RecyclerView.Adapter - implements ContextualCardUpdateListener, DismissalItemTouchHelperListener { + implements ContextualCardUpdateListener, SwipeDismissalDelegate.Listener { static final int SPAN_COUNT = 2; private static final String TAG = "ContextualCardsAdapter"; @@ -140,6 +140,9 @@ public class ContextualCardsAdapter extends RecyclerView.Adapter { - flipCardToDismissalView(holder); - mFlippedCardSet.add(holder); - return true; - }); - + private void initDismissalActions(RecyclerView.ViewHolder holder, ContextualCard card) { final Button btnKeep = holder.itemView.findViewById(R.id.keep); btnKeep.setOnClickListener(v -> { mFlippedCardSet.remove(holder); diff --git a/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java b/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java index a4186b049c0..121d4aadd5b 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java @@ -18,31 +18,62 @@ package com.android.settings.homepage.contextualcards.slices; import android.content.Context; import android.graphics.Canvas; +import android.widget.ViewFlipper; import androidx.annotation.NonNull; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; +import com.android.settings.R; +import com.android.settings.homepage.contextualcards.ContextualCard; + public class SwipeDismissalDelegate extends ItemTouchHelper.Callback { private static final String TAG = "DismissItemTouchHelper"; - public interface DismissalItemTouchHelperListener { + public interface Listener { void onSwiped(int position); } private final Context mContext; - private final DismissalItemTouchHelperListener mListener; + private final SwipeDismissalDelegate.Listener mListener; - public SwipeDismissalDelegate(Context context, DismissalItemTouchHelperListener listener) { + public SwipeDismissalDelegate(Context context, SwipeDismissalDelegate.Listener listener) { mContext = context; mListener = listener; } + /** + * Determine whether the ability to drag or swipe should be enabled or not. + * + * Only allow swipe on {@link ContextualCard} built with view type + * {@link SliceContextualCardRenderer#VIEW_TYPE_FULL_WIDTH} or + * {@link SliceContextualCardRenderer#VIEW_TYPE_HALF_WIDTH}. + * + * When the dismissal view is displayed, the swipe will also be disabled. + */ @Override public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { - return 0; + switch (viewHolder.getItemViewType()) { + case SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH: + case SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH: + //TODO(b/129438972): Convert this to a regular view. + final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.view_flipper); + + // As we are using ViewFlipper to switch between the initial view and + // dismissal view, here we are making sure the current displayed view is the + // initial view of either slice full card or half card, and only allow swipe on + // these two types. + if (viewFlipper.getCurrentView().getId() != getInitialViewId(viewHolder)) { + // Disable swiping when we are in the dismissal view + return 0; + } + return makeMovementFlags(0 /*dragFlags*/, + ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT /*swipeFlags*/); + default: + return 0; + } } @Override @@ -63,4 +94,11 @@ public class SwipeDismissalDelegate extends ItemTouchHelper.Callback { boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } + + private int getInitialViewId(RecyclerView.ViewHolder viewHolder) { + if (viewHolder.getItemViewType() == SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH) { + return R.id.content; + } + return R.id.slice_view; + } } \ No newline at end of file diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java index e08d845196e..a53ade2d4fd 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java @@ -16,13 +16,11 @@ package com.android.settings.homepage.contextualcards.slices; -import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_DEFERRED_SETUP; import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.Activity; @@ -118,34 +116,25 @@ public class SliceContextualCardRendererTest { } @Test - public void longClick_shouldFlipCard() { + public void bindView_isPendingDismiss_shouldFlipToDismissalView() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - final View card = viewHolder.itemView.findViewById(R.id.slice_view); final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.view_flipper); final View dismissalView = viewHolder.itemView.findViewById(R.id.dismissal_view); - mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); + final ContextualCard card = buildContextualCard( + TEST_SLICE_URI).mutate().setIsPendingDismiss(true).build(); - card.performLongClick(); + mRenderer.bindView(viewHolder, card); assertThat(viewFlipper.getCurrentView()).isEqualTo(dismissalView); } @Test - public void longClick_deferredSetupCard_shouldNotBeClickable() { - final RecyclerView.ViewHolder viewHolder = getDeferredSetupViewHolder(); - final View contentView = viewHolder.itemView.findViewById(R.id.content); - mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); - - assertThat(contentView.isLongClickable()).isFalse(); - } - - @Test - public void longClick_shouldAddViewHolderToSet() { + public void bindView_isPendingDismiss_shouldAddViewHolderToSet() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - final View card = viewHolder.itemView.findViewById(R.id.slice_view); - mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); + final ContextualCard card = buildContextualCard( + TEST_SLICE_URI).mutate().setIsPendingDismiss(true).build(); - card.performLongClick(); + mRenderer.bindView(viewHolder, card); assertThat(mRenderer.mFlippedCardSet).contains(viewHolder); } @@ -232,18 +221,6 @@ public class SliceContextualCardRendererTest { return mRenderer.createViewHolder(view, VIEW_TYPE_FULL_WIDTH); } - private RecyclerView.ViewHolder getDeferredSetupViewHolder() { - final RecyclerView recyclerView = new RecyclerView(mActivity); - recyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); - final View view = LayoutInflater.from(mActivity).inflate(VIEW_TYPE_DEFERRED_SETUP, - recyclerView, false); - final RecyclerView.ViewHolder viewHolder = spy( - mRenderer.createViewHolder(view, VIEW_TYPE_DEFERRED_SETUP)); - doReturn(VIEW_TYPE_DEFERRED_SETUP).when(viewHolder).getItemViewType(); - - return viewHolder; - } - private ContextualCard buildContextualCard(Uri sliceUri) { return new ContextualCard.Builder() .setName("test_name") diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java new file mode 100644 index 00000000000..00b7815745b --- /dev/null +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegateTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2019 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.homepage.contextualcards.slices; + +import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_DEFERRED_SETUP; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ViewFlipper; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settings.R; +import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer; +import com.android.settings.homepage.contextualcards.conditional.ConditionContextualCardRenderer.ConditionalCardHolder; +import com.android.settings.homepage.contextualcards.slices.SliceDeferredSetupCardRendererHelper.DeferredSetupCardViewHolder; +import com.android.settings.homepage.contextualcards.slices.SliceFullCardRendererHelper.SliceViewHolder; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.android.controller.ActivityController; + +@RunWith(RobolectricTestRunner.class) +public class SwipeDismissalDelegateTest { + + @Mock + private SwipeDismissalDelegate.Listener mDismissalDelegateListener; + + private Activity mActivity; + private RecyclerView mRecyclerView; + private SwipeDismissalDelegate mDismissalDelegate; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + final ActivityController activityController = Robolectric.buildActivity( + Activity.class); + mActivity = activityController.get(); + mActivity.setTheme(R.style.Theme_Settings_Home); + activityController.create(); + mRecyclerView = new RecyclerView(mActivity); + mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); + mDismissalDelegate = new SwipeDismissalDelegate(mActivity, mDismissalDelegateListener); + } + + @Test + public void getMovementFlags_conditionalViewHolder_shouldDisableSwipe() { + assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, getConditionalViewHolder())) + .isEqualTo(0); + } + + @Test + public void getMovementFlags_deferredSetupViewHolder_shouldDisableSwipe() { + assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, getDeferredSetupViewHolder())) + .isEqualTo(0); + } + + @Test + public void getMovementFlags_dismissalView_shouldDisableSwipe() { + final RecyclerView.ViewHolder holder = getSliceViewHolder(); + final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper); + viewFlipper.showNext(); + final View dismissalView = holder.itemView.findViewById(R.id.dismissal_view); + + assertThat(viewFlipper.getCurrentView()).isEqualTo(dismissalView); + assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, holder)).isEqualTo(0); + } + + @Test + public void getMovementFlags_SliceViewHolder_shouldEnableSwipe() { + final RecyclerView.ViewHolder holder = getSliceViewHolder(); + final ViewFlipper viewFlipper = holder.itemView.findViewById(R.id.view_flipper); + viewFlipper.setDisplayedChild(0); + final View sliceView = holder.itemView.findViewById(R.id.slice_view); + + assertThat(viewFlipper.getCurrentView()).isEqualTo(sliceView); + assertThat(mDismissalDelegate.getMovementFlags(mRecyclerView, getSliceViewHolder())) + .isNotEqualTo(0); + } + + @Test + public void onSwipe_shouldNotifyListener() { + mDismissalDelegate.onSwiped(getSliceViewHolder(), 1); + + verify(mDismissalDelegateListener).onSwiped(anyInt()); + } + + private RecyclerView.ViewHolder getSliceViewHolder() { + final View view = LayoutInflater.from(mActivity) + .inflate(SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH, mRecyclerView, false); + final RecyclerView.ViewHolder viewHolder = spy(new SliceViewHolder(view)); + doReturn(SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH).when( + viewHolder).getItemViewType(); + + return viewHolder; + } + + private RecyclerView.ViewHolder getConditionalViewHolder() { + final View view = LayoutInflater.from(mActivity) + .inflate(ConditionContextualCardRenderer.VIEW_TYPE_FULL_WIDTH, mRecyclerView, + false); + final RecyclerView.ViewHolder viewHolder = spy(new ConditionalCardHolder(view)); + doReturn(ConditionContextualCardRenderer.VIEW_TYPE_FULL_WIDTH).when( + viewHolder).getItemViewType(); + + return viewHolder; + } + + private RecyclerView.ViewHolder getDeferredSetupViewHolder() { + final View view = LayoutInflater.from(mActivity) + .inflate(VIEW_TYPE_DEFERRED_SETUP, mRecyclerView, false); + final RecyclerView.ViewHolder viewHolder = spy(new DeferredSetupCardViewHolder(view)); + doReturn(VIEW_TYPE_DEFERRED_SETUP).when(viewHolder).getItemViewType(); + + return viewHolder; + } +} From 69b6e0af775aeb6dfca275b76d0095782d259d8e Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Thu, 28 Mar 2019 14:29:56 +0800 Subject: [PATCH 17/43] Remove additional divider from Battery settings Fixes: 129351013 Test: visual Change-Id: I586ecdfeb82104ea35842bb12a2c90c29cca70ae --- res/xml/power_usage_summary.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/res/xml/power_usage_summary.xml b/res/xml/power_usage_summary.xml index 94ead86f0bc..88f88f106d1 100644 --- a/res/xml/power_usage_summary.xml +++ b/res/xml/power_usage_summary.xml @@ -26,13 +26,11 @@ android:title="@string/summary_placeholder" android:selectable="false" android:layout="@layout/battery_header" - settings:allowDividerBelow="true" settings:controller="com.android.settings.fuelgauge.BatteryHeaderPreferenceController" /> Date: Tue, 26 Mar 2019 20:14:58 +0800 Subject: [PATCH 18/43] Add search index into WallpaperSuggestionActivity .Use the Indexable interface for WallpaperSuggestionActivity and implement the getRawDataToIndex .Remove the search index in the WallpaperTypeSettings .Add full class name of the WallpaperTypeSettings into grandfather_not_implementing_index_provider Bug: 63117104 Test: robotest & Manual View Change-Id: I73f43e4996efbe857d74df297ea0d08eb1b640ac --- AndroidManifest.xml | 3 +- .../WallpaperSuggestionActivity.java | 36 +++++++++++++- .../wallpaper/WallpaperTypeSettings.java | 48 ------------------- ...randfather_not_implementing_index_provider | 1 + 4 files changed, 38 insertions(+), 50 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 9f6cdb50487..c83c14d4ceb 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -795,7 +795,8 @@ + android:icon="@drawable/ic_wallpaper" + android:exported="true"> diff --git a/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java b/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java index 14564b1d575..07138725d98 100644 --- a/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java +++ b/src/com/android/settings/wallpaper/WallpaperSuggestionActivity.java @@ -28,10 +28,18 @@ import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.core.SubSettingLauncher; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.search.Indexable; +import com.android.settings.search.SearchIndexableRaw; +import com.android.settingslib.search.SearchIndexable; import com.google.android.setupcompat.util.WizardManagerHelper; -public class WallpaperSuggestionActivity extends Activity { +import java.util.ArrayList; +import java.util.List; + +@SearchIndexable +public class WallpaperSuggestionActivity extends Activity implements Indexable { @Override protected void onCreate(Bundle savedInstanceState) { @@ -79,4 +87,30 @@ public class WallpaperSuggestionActivity extends Activity { return context.getResources().getBoolean( com.android.internal.R.bool.config_enableWallpaperService); } + + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + private static final String SUPPORT_SEARCH_INDEX_KEY = "wallpaper_type"; + + @Override + public List getRawDataToIndex(Context context, + boolean enabled) { + + final List result = new ArrayList<>(); + + SearchIndexableRaw data = new SearchIndexableRaw(context); + data.title = context.getString(R.string.wallpaper_settings_fragment_title); + data.screenTitle = context.getString( + R.string.wallpaper_settings_fragment_title); + data.intentTargetPackage = context.getString( + R.string.config_wallpaper_picker_package); + data.intentTargetClass = context.getString( + R.string.config_wallpaper_picker_class); + data.intentAction = Intent.ACTION_MAIN; + data.key = SUPPORT_SEARCH_INDEX_KEY; + result.add(data); + + return result; + } + }; } diff --git a/src/com/android/settings/wallpaper/WallpaperTypeSettings.java b/src/com/android/settings/wallpaper/WallpaperTypeSettings.java index 0e0f8df4448..2d4a16f2bd9 100644 --- a/src/com/android/settings/wallpaper/WallpaperTypeSettings.java +++ b/src/com/android/settings/wallpaper/WallpaperTypeSettings.java @@ -17,21 +17,11 @@ package com.android.settings.wallpaper; import android.app.settings.SettingsEnums; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settings.search.SearchIndexableRaw; -import com.android.settingslib.search.SearchIndexable; -import java.util.ArrayList; -import java.util.List; -@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) public class WallpaperTypeSettings extends DashboardFragment { private static final String TAG = "WallpaperTypeSettings"; @@ -54,42 +44,4 @@ public class WallpaperTypeSettings extends DashboardFragment { protected int getPreferenceScreenResId() { return R.xml.wallpaper_settings; } - - public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = - new BaseSearchIndexProvider() { - @Override - public List getRawDataToIndex(Context context, boolean enabled) { - final List result = new ArrayList<>(); - - final Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER); - final PackageManager pm = context.getPackageManager(); - final List rList = pm.queryIntentActivities(intent, - PackageManager.MATCH_DEFAULT_ONLY); - - // Add indexable data for package that is in config_wallpaper_picker_package - final String wallpaperPickerPackage = - context.getString(R.string.config_wallpaper_picker_package); - for (ResolveInfo info : rList) { - if (!wallpaperPickerPackage.equals(info.activityInfo.packageName)) { - continue; - } - CharSequence label = info.loadLabel(pm); - if (label == null) { - label = info.activityInfo.packageName; - } - final SearchIndexableRaw data = new SearchIndexableRaw(context); - data.title = label.toString(); - data.key = "wallpaper_type_settings"; - data.screenTitle = context.getResources().getString( - R.string.wallpaper_settings_fragment_title); - data.intentAction = Intent.ACTION_SET_WALLPAPER; - data.intentTargetPackage = info.activityInfo.packageName; - data.intentTargetClass = info.activityInfo.name; - data.keywords = context.getString(R.string.keywords_wallpaper); - result.add(data); - } - - return result; - } - }; } diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 8ca65ffb084..d7e558558d7 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -75,6 +75,7 @@ com.android.settings.users.RestrictedProfileSettings com.android.settings.users.UserDetailsSettings com.android.settings.vpn2.AppManagementFragment com.android.settings.vpn2.VpnSettings +com.android.settings.wallpaper.WallpaperTypeSettings com.android.settings.wifi.calling.WifiCallingSettingsForSub com.android.settings.wifi.ChangeWifiStateDetails com.android.settings.wifi.details.WifiNetworkDetailsFragment From e4b3844d0bdf5179ce3775abfe30441b0a3016ba Mon Sep 17 00:00:00 2001 From: cosmohsieh Date: Tue, 26 Mar 2019 14:14:52 +0800 Subject: [PATCH 19/43] Show "Disconnect" on forget button for ephemeral network For app or carrier initiated wifi connections (i.e all "ephemeral networks"), the detailed page of settings should show "Disconnect" instead of "Forget". Bug: 123892605 Test: atest WifiDetailPreferenceControllerTest Change-Id: I1ee7f45ea12fe2ab9ca35cab77de0b1d67efc1db --- res/values/strings.xml | 3 +++ .../WifiDetailPreferenceController.java | 20 ++++++++++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index dd6d7a3b0f8..d0a19b6906a 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -11030,4 +11030,7 @@ Automatically capture system heap dumps Automatically capture a heap dump for Android System when it uses too much memory + + + Disconnect diff --git a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java index 955525dd859..11ebf371b16 100644 --- a/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java +++ b/src/com/android/settings/wifi/details/WifiDetailPreferenceController.java @@ -158,6 +158,7 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController private final WifiTracker mWifiTracker; private final MetricsFeatureProvider mMetricsFeatureProvider; private boolean mIsOutOfRange; + private boolean mIsEphemeral; private boolean mConnected; private int mConnectingState; private WifiManager.ActionListener mConnectListener; @@ -248,12 +249,14 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController @Override public void onLost(Network network) { - // If support detail page for saved network, should update as disconnect but not exit. - if (SavedAccessPointsWifiSettings.usingDetailsFragment(mContext)) { - return; - } + final boolean lostCurrentNetwork = network.equals(mNetwork); + if (lostCurrentNetwork) { + // If support detail page for saved network, should update as disconnect but not + // exit. Except for ephemeral network which should not show on saved network list. + if (SavedAccessPointsWifiSettings.usingDetailsFragment(mContext) && !mIsEphemeral) { + return; + } - if (network.equals(mNetwork)) { exitActivity(); } } @@ -347,6 +350,9 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController mWifiTracker = null; } mConnected = mAccessPoint.isActive(); + // When lost the network connection, WifiInfo/NetworkInfo will be clear. So causes we + // could not check if the AccessPoint is ephemeral. Need to cache it in first. + mIsEphemeral = mAccessPoint.isEphemeral(); mConnectingState = STATE_NONE; mConnectListener = new WifiManager.ActionListener() { @Override @@ -687,6 +693,10 @@ public class WifiDetailPreferenceController extends AbstractPreferenceController } private void refreshButtons() { + // Ephemeral network won't be removed permanently, but be putted in blacklist. + mButtonsPref.setButton1Text( + mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget); + mButtonsPref.setButton1Visible(canForgetNetwork()); mButtonsPref.setButton2Visible(canSignIntoNetwork()); mButtonsPref.setButton3Visible(canConnectNetwork()); From 87c7f56d3521e2519908e43ba3e820f4b5c7ace4 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Thu, 28 Mar 2019 15:18:40 +0800 Subject: [PATCH 20/43] Fix navigation back problem from Assist app Fixes: 127907113 Test: visual Change-Id: I07196ee9f92f9d84fa75eb32cb6564b362f1293d --- AndroidManifest.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4166d6f0624..421de25a03d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -607,8 +607,7 @@ + android:label="@string/assist_and_voice_input_title"> From b1d846cbbd0a37a97ef0eaa8b5b27157257d1768 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Mon, 25 Mar 2019 23:25:44 +0800 Subject: [PATCH 21/43] Add condition to ignore invalid packages in NotificationChannelSlice If all displayable notification channels of package are turned off, stop showing notification channel slice of package. Bug: 124480122 Test: robotests Change-Id: I3ce9ebc4cca18dac3b4b6f15c28c697b72df81e3 --- .../slices/NotificationChannelSlice.java | 45 +++++++------ .../slices/NotificationChannelSliceTest.java | 63 ++++++++++++++----- 2 files changed, 74 insertions(+), 34 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java b/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java index d174156dc67..0278f90776d 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java +++ b/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSlice.java @@ -176,15 +176,10 @@ public class NotificationChannelSlice implements CustomSliceable { .setSubtitle(getSubTitle(mPackageName, mUid)) .setPrimaryAction(getPrimarySliceAction(icon, title, getIntent()))); - // Get rows by notification channel. + // Add notification channel rows. final List rows = getNotificationChannelRows(packageInfo, icon); - - // Get displayable notification channel count. - final int channelCount = Math.min(rows.size(), DEFAULT_EXPANDED_ROW_COUNT); - - // According to the displayable channel count to add rows. - for (int i = 0; i < channelCount; i++) { - listBuilder.addRow(rows.get(i)); + for (ListBuilder.RowBuilder rowBuilder : rows) { + listBuilder.addRow(rowBuilder); } return listBuilder.build(); @@ -279,10 +274,9 @@ public class NotificationChannelSlice implements CustomSliceable { private List getNotificationChannelRows(PackageInfo packageInfo, IconCompat icon) { final List notificationChannelRows = new ArrayList<>(); - final List enabledChannels = getEnabledChannels(mPackageName, mUid, - mAppRow); + final List displayableChannels = getDisplayableChannels(mAppRow); - for (NotificationChannel channel : enabledChannels) { + for (NotificationChannel channel : displayableChannels) { notificationChannelRows.add(new ListBuilder.RowBuilder() .setTitle(channel.getName()) .setSubtitle(NotificationBackend.getSentSummary( @@ -356,10 +350,9 @@ public class NotificationChannelSlice implements CustomSliceable { title); } - private List getEnabledChannels(String packageName, int uid, - NotificationBackend.AppRow appRow) { + private List getDisplayableChannels(NotificationBackend.AppRow appRow) { final List channelGroupList = - mNotificationBackend.getGroups(packageName, uid).getList(); + mNotificationBackend.getGroups(appRow.pkg, appRow.uid).getList(); final List channels = channelGroupList.stream() .flatMap(group -> group.getChannels().stream().filter( channel -> isChannelEnabled(group, channel, appRow))) @@ -376,8 +369,11 @@ public class NotificationChannelSlice implements CustomSliceable { } // Sort the notification channels with notification sent count by descending. - return channelStates.stream().sorted(CHANNEL_STATE_COMPARATOR).map( - state -> state.getNotificationChannel()).collect(Collectors.toList()); + return channelStates.stream() + .sorted(CHANNEL_STATE_COMPARATOR) + .map(state -> state.getNotificationChannel()) + .limit(DEFAULT_EXPANDED_ROW_COUNT) + .collect(Collectors.toList()); } private PackageInfo getMaxSentNotificationsPackage(List packageInfoList) { @@ -391,10 +387,14 @@ public class NotificationChannelSlice implements CustomSliceable { for (PackageInfo packageInfo : packageInfoList) { final NotificationBackend.AppRow appRow = mNotificationBackend.loadAppRow(mContext, mContext.getPackageManager(), packageInfo); + // Ignore packages which are banned notifications or block all displayable channels. + if (appRow.banned || isAllChannelsBlocked(getDisplayableChannels(appRow))) { + continue; + } + // Get sent notification count from app. final int sentCount = appRow.sentByApp.sentCount; - if (!appRow.banned && sentCount >= MIN_NOTIFICATION_SENT_COUNT - && sentCount > maxSentCount) { + if (sentCount >= MIN_NOTIFICATION_SENT_COUNT && sentCount > maxSentCount) { maxSentCount = sentCount; maxSentCountPackage = packageInfo; mAppRow = appRow; @@ -404,6 +404,15 @@ public class NotificationChannelSlice implements CustomSliceable { return maxSentCountPackage; } + private boolean isAllChannelsBlocked(List channels) { + for (NotificationChannel channel : channels) { + if (channel.getImportance() != IMPORTANCE_NONE) { + return false; + } + } + return true; + } + protected CharSequence getSubTitle(String packageName, int uid) { final int channelCount = mNotificationBackend.getChannelCount(packageName, uid); diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSliceTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSliceTest.java index a744e68a64c..12513f67e1a 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSliceTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/NotificationChannelSliceTest.java @@ -16,6 +16,7 @@ package com.android.settings.homepage.contextualcards.slices; +import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.slice.Slice.HINT_LIST_ITEM; import static android.app.slice.SliceItem.FORMAT_SLICE; @@ -116,7 +117,8 @@ public class NotificationChannelSliceTest { public void getSlice_hasSuggestedApp_shouldHaveNotificationChannelTitle() { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); - mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */); + mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -130,7 +132,8 @@ public class NotificationChannelSliceTest { public void getSlice_hasSuggestedApp_shouldSortByNotificationSentCount() { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); - mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */); + mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -157,7 +160,8 @@ public class NotificationChannelSliceTest { public void getSlice_noRecentlyInstalledApp_shouldHaveNoSuggestedAppTitle() { addMockPackageToPackageManager(false /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); - mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */); + mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -169,7 +173,8 @@ public class NotificationChannelSliceTest { public void getSlice_noMultiChannelApp_shouldHaveNoSuggestedAppTitle() { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); - mockNotificationBackend(1 /* channelCount */, NOTIFICATION_COUNT, false /* banned */); + mockNotificationBackend(1 /* channelCount */, NOTIFICATION_COUNT, false /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -178,10 +183,12 @@ public class NotificationChannelSliceTest { } @Test + @Config(shadows = ShadowRestrictedLockUtilsInternal.class) public void getSlice_insufficientNotificationSentCount_shouldHaveNoSuggestedAppTitle() { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); - mockNotificationBackend(CHANNEL_COUNT, 1 /* notificationCount */, false /* banned */); + mockNotificationBackend(CHANNEL_COUNT, 1 /* notificationCount */, false /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -192,7 +199,8 @@ public class NotificationChannelSliceTest { @Test public void getSlice_isSystemApp_shouldHaveNoSuggestedAppTitle() { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_SYSTEM); - mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */); + mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -204,7 +212,8 @@ public class NotificationChannelSliceTest { public void getSlice_isNotificationBanned_shouldHaveNoSuggestedAppTitle() { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); - mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, true /* banned */); + mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, true /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -218,7 +227,7 @@ public class NotificationChannelSliceTest { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); mockNotificationBackend(NotificationChannelSlice.DEFAULT_EXPANDED_ROW_COUNT * 2, - NOTIFICATION_COUNT, false /* banned */); + NOTIFICATION_COUNT, false /* banned */, false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -234,7 +243,8 @@ public class NotificationChannelSliceTest { public void getSlice_channelCountIsLessThanDefaultRows_subTitleShouldNotHaveTapToManagerAll() { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); - mockNotificationBackend(CHANNEL_COUNT - 1, NOTIFICATION_COUNT, false /* banned */); + mockNotificationBackend(CHANNEL_COUNT - 1, NOTIFICATION_COUNT, false /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -249,7 +259,8 @@ public class NotificationChannelSliceTest { public void getSlice_channelCountIsEqualToDefaultRows_subTitleShouldNotHaveTapToManagerAll() { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); - mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */); + mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -263,7 +274,8 @@ public class NotificationChannelSliceTest { public void getSlice_channelCountIsMoreThanDefaultRows_subTitleShouldHaveTapToManagerAll() { addMockPackageToPackageManager(true /* isRecentlyInstalled */, ApplicationInfo.FLAG_INSTALLED); - mockNotificationBackend(CHANNEL_COUNT + 1, NOTIFICATION_COUNT, false /* banned */); + mockNotificationBackend(CHANNEL_COUNT + 1, NOTIFICATION_COUNT, false /* banned */, + false /* isChannelBlocked */); final Slice slice = mNotificationChannelSlice.getSlice(); @@ -273,6 +285,20 @@ public class NotificationChannelSliceTest { CHANNEL_COUNT + 1)); } + @Test + @Config(shadows = ShadowRestrictedLockUtilsInternal.class) + public void getSlice_isAllDisplayableChannelBlocked_shouldHaveNoSuggestedAppTitle() { + addMockPackageToPackageManager(true /* isRecentlyInstalled */, + ApplicationInfo.FLAG_INSTALLED); + mockNotificationBackend(CHANNEL_COUNT, NOTIFICATION_COUNT, false /* banned */, + true /* isChannelBlocked */); + + final Slice slice = mNotificationChannelSlice.getSlice(); + + final SliceMetadata metadata = SliceMetadata.from(mContext, slice); + assertThat(metadata.getTitle()).isEqualTo(mContext.getString(R.string.no_suggested_app)); + } + private void addMockPackageToPackageManager(boolean isRecentlyInstalled, int flags) { final ApplicationInfo applicationInfo = new ApplicationInfo(); applicationInfo.name = APP_LABEL; @@ -294,8 +320,10 @@ public class NotificationChannelSliceTest { return System.currentTimeMillis(); } - private void mockNotificationBackend(int channelCount, int notificationCount, boolean banned) { - final List channels = buildNotificationChannel(channelCount); + private void mockNotificationBackend(int channelCount, int notificationCount, boolean banned, + boolean isChannelBlocked) { + final List channels = buildNotificationChannel(channelCount, + isChannelBlocked); final AppRow appRow = buildAppRow(channelCount, notificationCount, banned); doReturn(buildNotificationChannelGroups(channels)).when(mNotificationBackend).getGroups( @@ -308,6 +336,8 @@ public class NotificationChannelSliceTest { private AppRow buildAppRow(int channelCount, int sentCount, boolean banned) { final AppRow appRow = new AppRow(); + appRow.pkg = PACKAGE_NAME; + appRow.uid = UID; appRow.banned = banned; appRow.channelCount = channelCount; appRow.sentByApp = new NotificationsSentState(); @@ -317,11 +347,12 @@ public class NotificationChannelSliceTest { return appRow; } - private List buildNotificationChannel(int channelCount) { + private List buildNotificationChannel(int channelCount, + boolean isChannelBlock) { final List channels = new ArrayList<>(); for (int i = 0; i < channelCount; i++) { channels.add(new NotificationChannel(CHANNEL_NAME_PREFIX + i, CHANNEL_NAME_PREFIX + i, - IMPORTANCE_NONE)); + isChannelBlock ? IMPORTANCE_NONE : IMPORTANCE_LOW)); } return channels; @@ -369,4 +400,4 @@ public class NotificationChannelSliceTest { // Index 0: title; Index 1: summary. return rowSliceItems.get(1).getText(); } -} \ No newline at end of file +} From ab0bc669aed36e7d2e5abd8af947c7adceffc63c Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Wed, 27 Mar 2019 19:29:00 +0800 Subject: [PATCH 22/43] Add prepainting background while swiping to dismiss. When users swipe the card, they will see a pre-painted background. The dismissal view will appear in only after the swipe. Bug: 126214056 Test: visual Change-Id: I0eb308700cda2e6d72c718f3057775d3620267cd --- res/values/dimens.xml | 1 + res/values/styles.xml | 2 +- .../slices/SwipeDismissalDelegate.java | 38 ++++++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 4a65bc9f8c9..034e4a36b92 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -346,6 +346,7 @@ 14dp 16dp 16dp + @*android:dimen/config_dialogCornerRadius 12dp 12dp 16dp diff --git a/res/values/styles.xml b/res/values/styles.xml index 02db995d39b..8cd22fe4e96 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -418,7 +418,7 @@ @dimen/homepage_card_side_margin @dimen/homepage_card_side_margin @color/contextual_card_background - @*android:dimen/config_dialogCornerRadius + @dimen/homepage_card_corner_radius 0dp @color/homepage_card_stroke_color 1dp diff --git a/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java b/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java index 121d4aadd5b..3564189cc50 100644 --- a/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java +++ b/src/com/android/settings/homepage/contextualcards/slices/SwipeDismissalDelegate.java @@ -18,6 +18,10 @@ package com.android.settings.homepage.contextualcards.slices; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; +import android.view.View; import android.widget.ViewFlipper; import androidx.annotation.NonNull; @@ -29,7 +33,7 @@ import com.android.settings.homepage.contextualcards.ContextualCard; public class SwipeDismissalDelegate extends ItemTouchHelper.Callback { - private static final String TAG = "DismissItemTouchHelper"; + private static final String TAG = "SwipeDismissalDelegate"; public interface Listener { void onSwiped(int position); @@ -37,10 +41,18 @@ public class SwipeDismissalDelegate extends ItemTouchHelper.Callback { private final Context mContext; private final SwipeDismissalDelegate.Listener mListener; + private final Drawable mIconDelete; + private final Paint mBgPaint; + private final int mBgCornerRadius; public SwipeDismissalDelegate(Context context, SwipeDismissalDelegate.Listener listener) { mContext = context; mListener = listener; + mIconDelete = mContext.getDrawable(R.drawable.ic_delete); + mBgPaint = new Paint(); + mBgPaint.setColor(mContext.getColor(R.color.homepage_card_dismissal_background)); + mBgCornerRadius = mContext.getResources() + .getDimensionPixelSize(R.dimen.homepage_card_corner_radius); } /** @@ -93,6 +105,30 @@ public class SwipeDismissalDelegate extends ItemTouchHelper.Callback { @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + + final View itemView = viewHolder.itemView; + final int iconMargin = mContext.getResources() + .getDimensionPixelSize(R.dimen.homepage_card_dismissal_side_margin); + final int iconTop = + itemView.getTop() + (itemView.getHeight() - mIconDelete.getIntrinsicHeight()) / 2; + final int iconBottom = iconTop + mIconDelete.getIntrinsicHeight(); + + if (dX > 0) { //swipe to the right + final int iconLeft = itemView.getLeft() + iconMargin; + final int iconRight = iconLeft + mIconDelete.getIntrinsicWidth(); + final RectF rect = new RectF(itemView.getLeft(), itemView.getTop(), + itemView.getLeft() + ((int) dX) + mBgCornerRadius, itemView.getBottom()); + mIconDelete.setBounds(iconLeft, iconTop, iconRight, iconBottom); + c.drawRoundRect(rect, mBgCornerRadius, mBgCornerRadius, mBgPaint); + } else if (dX < 0) { + final int iconRight = itemView.getRight() - iconMargin; + final int iconLeft = iconRight - mIconDelete.getIntrinsicWidth(); + final RectF rect = new RectF(itemView.getRight() + ((int) dX), itemView.getTop(), + itemView.getRight(), itemView.getBottom()); + mIconDelete.setBounds(iconLeft, iconTop, iconRight, iconBottom); + c.drawRoundRect(rect, mBgCornerRadius, mBgCornerRadius, mBgPaint); + } + mIconDelete.draw(c); } private int getInitialViewId(RecyclerView.ViewHolder viewHolder) { From 59d4681253ec2024bb69199566844908ab86558d Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Mon, 25 Mar 2019 19:33:30 +0800 Subject: [PATCH 23/43] Handle When received intent extra in System settings need show dialog Bug: 128853573 Test: visual, robotest Change-Id: I92337e26f51c3f7ef4033d424ddcf07c6da6805d --- .../settings/aware/AwareFeatureProvider.java | 5 +++ .../aware/AwareFeatureProviderImpl.java | 6 +++ .../system/SystemDashboardFragment.java | 15 +++++++ .../system/SystemDashboardFragmentTest.java | 45 +++++++++++++++++-- 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/aware/AwareFeatureProvider.java b/src/com/android/settings/aware/AwareFeatureProvider.java index a24233f66b6..bda27c7143c 100644 --- a/src/com/android/settings/aware/AwareFeatureProvider.java +++ b/src/com/android/settings/aware/AwareFeatureProvider.java @@ -18,10 +18,15 @@ package com.android.settings.aware; import android.content.Context; +import androidx.fragment.app.Fragment; + public interface AwareFeatureProvider { /** Returns true if the aware sensor is supported. */ boolean isSupported(Context context); /** Returns true if the aware feature is enabled. */ boolean isEnabled(Context context); + + /** Show information dialog. */ + void showRestrictionDialog(Fragment parent); } diff --git a/src/com/android/settings/aware/AwareFeatureProviderImpl.java b/src/com/android/settings/aware/AwareFeatureProviderImpl.java index 44ec31b45f0..5d160313869 100644 --- a/src/com/android/settings/aware/AwareFeatureProviderImpl.java +++ b/src/com/android/settings/aware/AwareFeatureProviderImpl.java @@ -18,6 +18,8 @@ package com.android.settings.aware; import android.content.Context; +import androidx.fragment.app.Fragment; + public class AwareFeatureProviderImpl implements AwareFeatureProvider { @Override public boolean isSupported(Context context) { @@ -28,4 +30,8 @@ public class AwareFeatureProviderImpl implements AwareFeatureProvider { public boolean isEnabled(Context context) { return false; } + + @Override + public void showRestrictionDialog(Fragment parent) { + } } diff --git a/src/com/android/settings/system/SystemDashboardFragment.java b/src/com/android/settings/system/SystemDashboardFragment.java index 5c642b234ac..3ab31e3a17e 100644 --- a/src/com/android/settings/system/SystemDashboardFragment.java +++ b/src/com/android/settings/system/SystemDashboardFragment.java @@ -20,12 +20,14 @@ import android.content.Context; import android.os.Bundle; import android.provider.SearchIndexableResource; +import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settingslib.search.SearchIndexable; @@ -40,6 +42,8 @@ public class SystemDashboardFragment extends DashboardFragment { private static final String KEY_RESET = "reset_dashboard"; + public static final String EXTRA_SHOW_AWARE_DISABLED = "show_aware_dialog_disabled"; + @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -49,6 +53,17 @@ public class SystemDashboardFragment extends DashboardFragment { if (getVisiblePreferenceCount(screen) == screen.getInitialExpandedChildrenCount() + 1) { screen.setInitialExpandedChildrenCount(Integer.MAX_VALUE); } + + showRestrictionDialog(); + } + + @VisibleForTesting + public void showRestrictionDialog() { + final Bundle args = getArguments(); + if (args != null && args.getBoolean(EXTRA_SHOW_AWARE_DISABLED, false)) { + FeatureFactory.getFactory(getContext()).getAwareFeatureProvider() + .showRestrictionDialog(this); + } } @Override diff --git a/tests/robotests/src/com/android/settings/system/SystemDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/system/SystemDashboardFragmentTest.java index 75cd552ab01..7bdc368364b 100644 --- a/tests/robotests/src/com/android/settings/system/SystemDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/system/SystemDashboardFragmentTest.java @@ -18,8 +18,17 @@ package com.android.settings.system; import static com.google.common.truth.Truth.assertThat; -import android.content.Context; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import android.content.Context; +import android.os.Bundle; + +import com.android.settings.aware.AwareFeatureProvider; +import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.XmlTestUtils; import com.android.settings.testutils.shadow.SettingsShadowResources; import com.android.settings.testutils.shadow.ShadowUserManager; @@ -38,11 +47,17 @@ import java.util.List; @Config(shadows = {SettingsShadowResources.class, ShadowUserManager.class}) public class SystemDashboardFragmentTest { + private Context mContext; + private SystemDashboardFragment mFragment; + @Before public void setup() { SettingsShadowResources.overrideResource( com.android.internal.R.bool.config_supportSystemNavigationKeys, true); ShadowUserManager.getShadow().setIsAdminUser(true); + mContext = RuntimeEnvironment.application; + mFragment = spy(new SystemDashboardFragment()); + when(mFragment.getContext()).thenReturn(mContext); } @After @@ -52,13 +67,35 @@ public class SystemDashboardFragmentTest { @Test public void testNonIndexableKeys_existInXmlLayout() { - final Context context = RuntimeEnvironment.application; final List niks = SystemDashboardFragment.SEARCH_INDEX_DATA_PROVIDER - .getNonIndexableKeys(context); + .getNonIndexableKeys(mContext); final int xmlId = (new SystemDashboardFragment()).getPreferenceScreenResId(); - final List keys = XmlTestUtils.getKeysFromPreferenceXml(context, xmlId); + final List keys = XmlTestUtils.getKeysFromPreferenceXml(mContext, xmlId); assertThat(keys).containsAllIn(niks); } + + @Test + public void showRestrictionDialog_hasValidExtra_shouldShowDialog() { + final AwareFeatureProvider mProvider = + FakeFeatureFactory.setupForTest().mAwareFeatureProvider; + final Bundle bundle = new Bundle(); + bundle.putBoolean(SystemDashboardFragment.EXTRA_SHOW_AWARE_DISABLED, true); + when(mFragment.getArguments()).thenReturn(bundle); + + mFragment.showRestrictionDialog(); + + verify(mProvider).showRestrictionDialog(any()); + } + + @Test + public void showRestrictionDialog_hasInvalidExtra_shouldNotShowDialog() { + final AwareFeatureProvider mProvider = + FakeFeatureFactory.setupForTest().mAwareFeatureProvider; + + mFragment.showRestrictionDialog(); + + verify(mProvider, never()).showRestrictionDialog(any()); + } } From 2512aa1ed63e976cf6505215b105048eac8b78c3 Mon Sep 17 00:00:00 2001 From: Joel Galenson Date: Wed, 27 Mar 2019 10:19:27 -0700 Subject: [PATCH 24/43] Add tiebreak rules for bar chart permissions. If multiple permissions have been used by the same number of apps, we apply a stable tiebreak so we can be consistent in different places. We prefer location, then microphone, then camera, and everything else is alphabetical. Test: View chart with ties. Test: atest PermissionBarChartPreferenceControllerTest Change-Id: I035ac19391788cc720e1c832bf8bd5a60d31e8ad --- ...ermissionBarChartPreferenceController.java | 28 +++++++++++++++-- ...ssionBarChartPreferenceControllerTest.java | 31 +++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java index b47ad9cc224..2920dcf271d 100644 --- a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java +++ b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java @@ -16,6 +16,10 @@ package com.android.settings.privacy; +import static android.Manifest.permission_group.CAMERA; +import static android.Manifest.permission_group.LOCATION; +import static android.Manifest.permission_group.MICROPHONE; + import static com.android.settingslib.widget.BarChartPreference.MAXIMUM_BAR_VIEWS; import static java.util.concurrent.TimeUnit.DAYS; @@ -131,8 +135,28 @@ public class PermissionBarChartPreferenceController extends BasePreferenceContro @Override public void onPermissionUsageResult(@NonNull List usageInfos) { - usageInfos.sort(Comparator.comparingInt( - RuntimePermissionUsageInfo::getAppAccessCount).reversed()); + usageInfos.sort((x, y) -> { + int usageDiff = y.getAppAccessCount() - x.getAppAccessCount(); + if (usageDiff != 0) { + return usageDiff; + } + String xName = x.getName(); + String yName = y.getName(); + if (xName.equals(LOCATION)) { + return -1; + } else if (yName.equals(LOCATION)) { + return 1; + } else if (xName.equals(MICROPHONE)) { + return -1; + } else if (yName.equals(MICROPHONE)) { + return 1; + } else if (xName.equals(CAMERA)) { + return -1; + } else if (yName.equals(CAMERA)) { + return 1; + } + return x.getName().compareTo(y.getName()); + }); // If the result is different, we need to update bar views. if (!areSamePermissionGroups(usageInfos)) { diff --git a/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java index 332156e8b9a..988816897a7 100644 --- a/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/privacy/PermissionBarChartPreferenceControllerTest.java @@ -16,6 +16,14 @@ package com.android.settings.privacy; +import static android.Manifest.permission_group.CALENDAR; +import static android.Manifest.permission_group.CAMERA; +import static android.Manifest.permission_group.CONTACTS; +import static android.Manifest.permission_group.LOCATION; +import static android.Manifest.permission_group.MICROPHONE; +import static android.Manifest.permission_group.PHONE; +import static android.Manifest.permission_group.SMS; + import static com.android.settings.core.BasePreferenceController.AVAILABLE_UNSEARCHABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; @@ -221,4 +229,27 @@ public class PermissionBarChartPreferenceControllerTest { verify(mFragment).setLoadingEnabled(false /* enabled */); verify(mPreference).updateLoadingState(false /* isLoading */); } + + @Test + public void onPermissionUsageResult_shouldBeSorted() { + final List infos = new ArrayList<>(); + infos.add(new RuntimePermissionUsageInfo(PHONE, 10)); + infos.add(new RuntimePermissionUsageInfo(LOCATION, 10)); + infos.add(new RuntimePermissionUsageInfo(CAMERA, 10)); + infos.add(new RuntimePermissionUsageInfo(SMS, 1)); + infos.add(new RuntimePermissionUsageInfo(MICROPHONE, 10)); + infos.add(new RuntimePermissionUsageInfo(CONTACTS, 42)); + infos.add(new RuntimePermissionUsageInfo(CALENDAR, 10)); + mController.displayPreference(mScreen); + + mController.onPermissionUsageResult(infos); + + assertThat(infos.get(0).getName()).isEqualTo(CONTACTS); + assertThat(infos.get(1).getName()).isEqualTo(LOCATION); + assertThat(infos.get(2).getName()).isEqualTo(MICROPHONE); + assertThat(infos.get(3).getName()).isEqualTo(CAMERA); + assertThat(infos.get(4).getName()).isEqualTo(CALENDAR); + assertThat(infos.get(5).getName()).isEqualTo(PHONE); + assertThat(infos.get(6).getName()).isEqualTo(SMS); + } } From 9a2017ef57f4f0c8280c76e70c73ab53fe8068a7 Mon Sep 17 00:00:00 2001 From: Joel Galenson Date: Thu, 28 Mar 2019 08:04:59 -0700 Subject: [PATCH 25/43] When filtering permission usages by a single permission, default to last day. The general link to the dashboard shows only the last day, but clicking on an individual permission did not. Fixes: 129471807 Test: Click on permission. Change-Id: Ice3b5b1ac6e2cb5a49cabf3c56167c9d39d3c3a1 --- .../settings/privacy/PermissionBarChartPreferenceController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java index b47ad9cc224..2c22e066f26 100644 --- a/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java +++ b/src/com/android/settings/privacy/PermissionBarChartPreferenceController.java @@ -176,6 +176,7 @@ public class PermissionBarChartPreferenceController extends BasePreferenceContro barViewInfos[index].setClickListener((View v) -> { final Intent intent = new Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE); intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permissionGroupInfo.getName()); + intent.putExtra(Intent.EXTRA_DURATION_MILLIS, DAYS.toMillis(1)); mContext.startActivity(intent); }); } From 3bfbd1c8f142aab786c91a087b5aa5d977880428 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Thu, 28 Mar 2019 10:13:00 -0700 Subject: [PATCH 26/43] Update Content Capture strings. Also moved it down to the bottom of the screen... Bug: 129403630 Test: manual verification Change-Id: Ie21701035717f43c1af9f7040aa71a8c8c91b17d --- res/values/strings.xml | 8 +++--- res/xml/privacy_dashboard_settings.xml | 40 +++++++++++++------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 11281c285c7..af90b0bb5b6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -11011,11 +11011,11 @@ You may lose access to any remaining time or data. Check with your provider before removing. - - content capture + + content capture, smart suggestions - Content Capture - + Smart Suggestions + Allow Android to save information seen on your screen or heard in video or audio content. Android makes helpful suggestions based on your device activity. diff --git a/res/xml/privacy_dashboard_settings.xml b/res/xml/privacy_dashboard_settings.xml index f39880c2bd7..45b9d1955e3 100644 --- a/res/xml/privacy_dashboard_settings.xml +++ b/res/xml/privacy_dashboard_settings.xml @@ -62,26 +62,6 @@ android:summary="@string/summary_placeholder" settings:searchable="false"/> - - - - - - - - - @@ -105,4 +85,24 @@ settings:searchable="false"/> + + + + + + + + + \ No newline at end of file From 85717d9f8d17a153fe3e7c13d24e7f7b205cdcdd Mon Sep 17 00:00:00 2001 From: Antony Sargent Date: Thu, 28 Mar 2019 10:25:00 -0700 Subject: [PATCH 27/43] Use better string in dialog asking which SIM to use for SMS Right now when you have a dual-SIM device but no default set for which one to use for SMS, when you go to try and send an SMS you'll get a dialog asking which SIM to use with the generic string of "Select a SIM card". We recently added a more specific string for this in a separate CL, which reads "Select a SIM card for SMS". This CL switches the code to use that more specific string. Bug: 129021763 Test: compile / manual Change-Id: I7d12a7191de23366b0a6b6888021e57ff9ddced9 --- src/com/android/settings/sim/SimDialogActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/sim/SimDialogActivity.java b/src/com/android/settings/sim/SimDialogActivity.java index 13148c8de8f..487dace2fd0 100644 --- a/src/com/android/settings/sim/SimDialogActivity.java +++ b/src/com/android/settings/sim/SimDialogActivity.java @@ -254,7 +254,7 @@ public class SimDialogActivity extends Activity { builder.setTitle(R.string.select_sim_for_calls); break; case SMS_PICK: - builder.setTitle(R.string.sim_card_select_title); + builder.setTitle(R.string.select_sim_for_sms); break; default: throw new IllegalArgumentException("Invalid dialog type " From 185acfc5ae55ee6ff6678810ec60d48c94d7881d Mon Sep 17 00:00:00 2001 From: "Torne (Richard Coles)" Date: Thu, 28 Mar 2019 14:11:50 -0400 Subject: [PATCH 28/43] Remove WebView fallback package handling. There is no longer a special "fallback package" state for WebView that means it should not be able to have its enabled/disabled state controlled by the user. Remove the code used to implement this in Settings. Bug: 129470358 Test: verify that WebView's enable/disable button is not greyed out Change-Id: Ifc0921511a71282a77f239e5ff5955e60fab6e2c --- .../AppButtonsPreferenceController.java | 23 ------------------- .../manageapplications/ResetAppsHelper.java | 14 +---------- .../AppButtonsPreferenceControllerTest.java | 1 - 3 files changed, 1 insertion(+), 37 deletions(-) diff --git a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java index d9ddc2fc769..56d53358aa5 100644 --- a/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java +++ b/src/com/android/settings/applications/appinfo/AppButtonsPreferenceController.java @@ -33,13 +33,10 @@ import android.content.res.Resources; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import android.view.View; -import android.webkit.IWebViewUpdateService; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; @@ -438,10 +435,6 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp enabled = false; } - if (isFallbackPackage(mAppEntry.info.packageName)) { - enabled = false; - } - mButtonsPref.setButton2Enabled(enabled); } @@ -466,22 +459,6 @@ public class AppButtonsPreferenceController extends BasePreferenceController imp } } - @VisibleForTesting - boolean isFallbackPackage(String packageName) { - try { - IWebViewUpdateService webviewUpdateService = - IWebViewUpdateService.Stub.asInterface( - ServiceManager.getService("webviewupdate")); - if (webviewUpdateService.isFallbackPackage(packageName)) { - return true; - } - } catch (RemoteException e) { - throw new RuntimeException(e); - } - - return false; - } - @VisibleForTesting void updateForceStopButton() { if (mDpm.packageHasActiveAdmins(mPackageInfo.packageName)) { diff --git a/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java b/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java index 3af3500e48c..92c0958c640 100644 --- a/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java +++ b/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java @@ -32,7 +32,6 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; -import android.webkit.IWebViewUpdateService; import androidx.appcompat.app.AlertDialog; @@ -48,7 +47,6 @@ public class ResetAppsHelper implements DialogInterface.OnClickListener, private final PackageManager mPm; private final IPackageManager mIPm; private final INotificationManager mNm; - private final IWebViewUpdateService mWvus; private final NetworkPolicyManager mNpm; private final AppOpsManager mAom; private final Context mContext; @@ -61,7 +59,6 @@ public class ResetAppsHelper implements DialogInterface.OnClickListener, mIPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); mNm = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); - mWvus = IWebViewUpdateService.Stub.asInterface(ServiceManager.getService("webviewupdate")); mNpm = NetworkPolicyManager.from(context); mAom = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); } @@ -122,8 +119,7 @@ public class ResetAppsHelper implements DialogInterface.OnClickListener, } if (!app.enabled) { if (mPm.getApplicationEnabledSetting(app.packageName) - == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER - && !isNonEnableableFallback(app.packageName)) { + == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { mPm.setApplicationEnabledSetting(app.packageName, PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, PackageManager.DONT_KILL_APP); @@ -147,12 +143,4 @@ public class ResetAppsHelper implements DialogInterface.OnClickListener, } }); } - - private boolean isNonEnableableFallback(String packageName) { - try { - return mWvus.isFallbackPackage(packageName); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - } } diff --git a/tests/robotests/src/com/android/settings/applications/appinfo/AppButtonsPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/applications/appinfo/AppButtonsPreferenceControllerTest.java index 59aea3de99c..6d9430ccd26 100644 --- a/tests/robotests/src/com/android/settings/applications/appinfo/AppButtonsPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/applications/appinfo/AppButtonsPreferenceControllerTest.java @@ -119,7 +119,6 @@ public class AppButtonsPreferenceControllerTest { mController = spy(new AppButtonsPreferenceController(mSettingsActivity, mFragment, mLifecycle, PACKAGE_NAME, mState, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN)); - doReturn(false).when(mController).isFallbackPackage(anyString()); mAppEntry.info = mAppInfo; mAppInfo.packageName = PACKAGE_NAME; From e0f24e6f5e922e84d81510cea8833fa23157efb8 Mon Sep 17 00:00:00 2001 From: lindatseng Date: Wed, 27 Mar 2019 16:25:12 -0700 Subject: [PATCH 29/43] Make the text appearance consistant in add vpn dialog Set the password fields text appearance programatically to get the appearance consistant. The style set in xml will be overriden if the field is set to password. I set the text appearance programmatically in order to keep the appearance consistent with the others. The color of the text become gray after setting the text appearance for some reason. Note that I also do changes to the password field which is not mentioned in the bug. Test: visual inspection. Bug: 127728228 Change-Id: Ie206f60877eb39addc1370ae4e2aee379597960e --- src/com/android/settings/vpn2/ConfigDialog.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/com/android/settings/vpn2/ConfigDialog.java b/src/com/android/settings/vpn2/ConfigDialog.java index 07e957d6123..9f2176c4f02 100644 --- a/src/com/android/settings/vpn2/ConfigDialog.java +++ b/src/com/android/settings/vpn2/ConfigDialog.java @@ -143,6 +143,7 @@ class ConfigDialog extends AlertDialog implements TextWatcher, } mMppe.setChecked(mProfile.mppe); mL2tpSecret.setText(mProfile.l2tpSecret); + mL2tpSecret.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium); mIpsecIdentifier.setText(mProfile.ipsecIdentifier); mIpsecSecret.setText(mProfile.ipsecSecret); loadCertificates(mIpsecUserCert, Credentials.USER_PRIVATE_KEY, 0, mProfile.ipsecUserCert); @@ -152,6 +153,7 @@ class ConfigDialog extends AlertDialog implements TextWatcher, R.string.vpn_no_server_cert, mProfile.ipsecServerCert); mSaveLogin.setChecked(mProfile.saveLogin); mAlwaysOnVpn.setChecked(mProfile.key.equals(VpnUtils.getLockdownVpn())); + mPassword.setTextAppearance(android.R.style.TextAppearance_DeviceDefault_Medium); // Hide lockdown VPN on devices that require IMS authentication if (SystemProperties.getBoolean("persist.radio.imsregrequired", false)) { From a2297dc1d1117dcb0499503c3e46ed68524ee7ff Mon Sep 17 00:00:00 2001 From: Quang Luong Date: Thu, 21 Mar 2019 16:37:55 -0700 Subject: [PATCH 30/43] Add SSID to Network Details page for Passpoint Added SSID to Network Details page under the Advanced portion only when the network is for Passpoint, whose network title is not the SSID of the underlying connected AP. Bug: 129092023 Test: atest RunSettingsRoboTests Change-Id: I13308d46e727fc01fec7a079d8e32ab9f1763e21 --- res/values/strings.xml | 2 + res/xml/wifi_network_details_fragment.xml | 5 ++ .../WifiDetailPreferenceController.java | 15 ++++++ .../WifiDetailPreferenceControllerTest.java | 50 +++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index 11281c285c7..421706a7281 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -2284,6 +2284,8 @@ Advanced Wi\u2011Fi + + SSID MAC address diff --git a/res/xml/wifi_network_details_fragment.xml b/res/xml/wifi_network_details_fragment.xml index 782a7cffed4..8fb19cbc684 100644 --- a/res/xml/wifi_network_details_fragment.xml +++ b/res/xml/wifi_network_details_fragment.xml @@ -84,6 +84,11 @@ + Date: Thu, 28 Mar 2019 13:40:35 -0700 Subject: [PATCH 31/43] Add min width to switchbar toggle. Fixes: 127878068 Test: visual Change-Id: I77eb35ee7931cd6e4808192fe5ff4b700b51ccdb --- res/values/themes.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/res/values/themes.xml b/res/values/themes.xml index a8ae50638ee..d777fd0abb9 100644 --- a/res/values/themes.xml +++ b/res/values/themes.xml @@ -103,6 +103,7 @@ @color/switchbar_switch_track_tint @color/switchbar_switch_thumb_tint @dimen/min_tap_target_size + @dimen/min_tap_target_size - + diff --git a/tests/robotests/res/values/themes.xml b/tests/robotests/res/values/themes.xml index 41ace18a15e..92edb17b503 100644 --- a/tests/robotests/res/values/themes.xml +++ b/tests/robotests/res/values/themes.xml @@ -23,7 +23,7 @@ @android:color/white @dimen/switchbar_subsettings_margin_start @dimen/switchbar_subsettings_margin_end - @color/switch_bar_background + ?android:attr/textColorSecondary ?android:attr/colorAccent @drawable/ic_help From 4e48da99a9ed4b2a722c33e71f6c69830018c475 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Tue, 26 Mar 2019 09:04:22 -0400 Subject: [PATCH 34/43] Move bubble settings to their own page To make room for graphics/longer descriptive text Test: robotests Bug: 129068779 Change-Id: Iac6ea43bd3a0cddc487ff3d1bbd8f35142294d01 --- AndroidManifest.xml | 12 ++ res/values/strings.xml | 4 +- res/xml/app_bubble_notification_settings.xml | 34 ++++ res/xml/app_notification_settings.xml | 7 +- res/xml/bubble_notification_settings.xml | 33 ++++ res/xml/configure_notification_settings.xml | 6 +- src/com/android/settings/Settings.java | 1 + .../core/gateway/SettingsGateway.java | 4 + .../AppBubbleNotificationSettings.java | 104 ++++++++++++ .../notification/AppNotificationSettings.java | 2 +- ...ubbleNotificationPreferenceController.java | 2 +- .../BubbleNotificationSettings.java | 65 ++++++++ .../BubblePreferenceController.java | 3 + ...mmaryNotificationPreferenceController.java | 53 +++++++ .../BubbleSummaryPreferenceController.java | 99 ++++++++++++ .../BubblePreferenceControllerTest.java | 5 + ...yNotificationPreferenceControllerTest.java | 70 ++++++++ ...BubbleSummaryPreferenceControllerTest.java | 149 ++++++++++++++++++ 18 files changed, 642 insertions(+), 11 deletions(-) create mode 100644 res/xml/app_bubble_notification_settings.xml create mode 100644 res/xml/bubble_notification_settings.xml create mode 100644 src/com/android/settings/notification/AppBubbleNotificationSettings.java create mode 100644 src/com/android/settings/notification/BubbleNotificationSettings.java create mode 100644 src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java create mode 100644 src/com/android/settings/notification/BubbleSummaryPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index dbefdfa5e22..7b2935bfd0c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2454,6 +2454,18 @@ android:value="true" /> + + + + + + + + Allow notification dots - Allow bubbles + Bubbles - Allow apps to show some notifications as bubbles + Quickly access app content from anywhere using floating shortcuts Some notifications and other content can appear as bubbles on the screen. To open a bubble, tap it. To dismiss it, drag it down the screen. diff --git a/res/xml/app_bubble_notification_settings.xml b/res/xml/app_bubble_notification_settings.xml new file mode 100644 index 00000000000..8d97f8fda7a --- /dev/null +++ b/res/xml/app_bubble_notification_settings.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/res/xml/app_notification_settings.xml b/res/xml/app_notification_settings.xml index faad649a7ab..fedd3cc114f 100644 --- a/res/xml/app_notification_settings.xml +++ b/res/xml/app_notification_settings.xml @@ -60,11 +60,10 @@ settings:useAdditionalSummary="true" android:order="1001" settings:restrictedSwitchSummary="@string/enabled_by_admin" /> - + android:order="1002" /> + + + + + + + + + diff --git a/res/xml/configure_notification_settings.xml b/res/xml/configure_notification_settings.xml index e48ddc1f2bd..97a41822995 100644 --- a/res/xml/configure_notification_settings.xml +++ b/res/xml/configure_notification_settings.xml @@ -70,11 +70,11 @@ settings:controller="com.android.settings.notification.BadgingNotificationPreferenceController"/> - + settings:controller="com.android.settings.notification.BubbleSummaryNotificationPreferenceController" + android:fragment="com.android.settings.notification.BubbleNotificationSettings"/> createPreferenceControllers(Context context) { + mControllers = getPreferenceControllers(context, this); + return new ArrayList<>(mControllers); + } + + protected static List getPreferenceControllers( + Context context, AppBubbleNotificationSettings fragment) { + List controllers = new ArrayList<>(); + controllers.add(new HeaderPreferenceController(context, fragment)); + controllers.add(new BubblePreferenceController(context, new NotificationBackend())); + return controllers; + } + + @Override + public void onResume() { + super.onResume(); + + if (mUid < 0 || TextUtils.isEmpty(mPkg) || mPkgInfo == null) { + Log.w(TAG, "Missing package or uid or packageinfo"); + finish(); + return; + } + + for (NotificationPreferenceController controller : mControllers) { + controller.onResume(mAppRow, mChannel, mChannelGroup, mSuspendedAppsAdmin); + controller.displayPreference(getPreferenceScreen()); + } + updatePreferenceStates(); + } + + /** + * For Search. + */ + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.app_bubble_notification_settings; + return Arrays.asList(sir); + } + + @Override + public List createPreferenceControllers(Context + context) { + return new ArrayList<>(AppBubbleNotificationSettings.getPreferenceControllers( + context, null)); + } + }; +} diff --git a/src/com/android/settings/notification/AppNotificationSettings.java b/src/com/android/settings/notification/AppNotificationSettings.java index 3ccca000406..24d85e21f59 100644 --- a/src/com/android/settings/notification/AppNotificationSettings.java +++ b/src/com/android/settings/notification/AppNotificationSettings.java @@ -152,7 +152,7 @@ public class AppNotificationSettings extends NotificationSettingsBase { mControllers.add(new DescriptionPreferenceController(context)); mControllers.add(new NotificationsOffPreferenceController(context)); mControllers.add(new DeletedChannelsPreferenceController(context, mBackend)); - mControllers.add(new BubblePreferenceController(context, mBackend)); + mControllers.add(new BubbleSummaryPreferenceController(context, mBackend)); return new ArrayList<>(mControllers); } diff --git a/src/com/android/settings/notification/BubbleNotificationPreferenceController.java b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java index caba7d92cd9..83e73e9b841 100644 --- a/src/com/android/settings/notification/BubbleNotificationPreferenceController.java +++ b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java @@ -56,7 +56,7 @@ public class BubbleNotificationPreferenceController extends TogglePreferenceCont @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); - Preference preference = screen.findPreference(NOTIFICATION_BUBBLES); + Preference preference = screen.findPreference(getPreferenceKey()); if (preference != null) { mSettingObserver = new SettingObserver(preference); } diff --git a/src/com/android/settings/notification/BubbleNotificationSettings.java b/src/com/android/settings/notification/BubbleNotificationSettings.java new file mode 100644 index 00000000000..70442935cbd --- /dev/null +++ b/src/com/android/settings/notification/BubbleNotificationSettings.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 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.notification; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.provider.SearchIndexableResource; + +import com.android.settings.R; +import com.android.settings.core.OnActivityResultListener; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +import java.util.Arrays; +import java.util.List; + +@SearchIndexable +public class BubbleNotificationSettings extends DashboardFragment implements + OnActivityResultListener { + private static final String TAG = "BubbleNotiSettings"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.BUBBLE_SETTINGS; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.bubble_notification_settings; + } + + /** + * For Search. + */ + public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider() { + @Override + public List getXmlResourcesToIndex( + Context context, boolean enabled) { + final SearchIndexableResource sir = new SearchIndexableResource(context); + sir.xmlResId = R.xml.bubble_notification_settings; + return Arrays.asList(sir); + } + }; +} diff --git a/src/com/android/settings/notification/BubblePreferenceController.java b/src/com/android/settings/notification/BubblePreferenceController.java index 5b3be440622..5dab37445b8 100644 --- a/src/com/android/settings/notification/BubblePreferenceController.java +++ b/src/com/android/settings/notification/BubblePreferenceController.java @@ -21,6 +21,7 @@ import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; import android.content.Context; import android.provider.Settings; +import com.android.settings.R; import com.android.settings.core.PreferenceControllerMixin; import com.android.settingslib.RestrictedSwitchPreference; @@ -74,6 +75,8 @@ public class BubblePreferenceController extends NotificationPreferenceController pref.setEnabled(isChannelConfigurable() && !pref.isDisabledByAdmin()); } else { pref.setChecked(mAppRow.allowBubbles); + pref.setSummary(mContext.getString( + R.string.bubbles_app_toggle_summary, mAppRow.label)); } } } diff --git a/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java b/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java new file mode 100644 index 00000000000..e26d9a80331 --- /dev/null +++ b/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceController.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2019 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.notification; + +import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; + +import android.content.Context; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; + +import androidx.annotation.VisibleForTesting; + +public class BubbleSummaryNotificationPreferenceController extends BasePreferenceController { + + @VisibleForTesting + static final int ON = 1; + + public BubbleSummaryNotificationPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public CharSequence getSummary() { + return mContext.getString( + areBubblesEnabled() ? R.string.switch_on_text : R.string.switch_off_text); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + private boolean areBubblesEnabled() { + return Settings.Secure.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, ON) == ON; + } +} diff --git a/src/com/android/settings/notification/BubbleSummaryPreferenceController.java b/src/com/android/settings/notification/BubbleSummaryPreferenceController.java new file mode 100644 index 00000000000..708bbcded21 --- /dev/null +++ b/src/com/android/settings/notification/BubbleSummaryPreferenceController.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019 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.notification; + +import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; + +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.os.Bundle; +import android.provider.Settings; + +import com.android.settings.R; +import com.android.settings.applications.AppInfoBase; +import com.android.settings.core.SubSettingLauncher; + +import androidx.preference.Preference; + +public class BubbleSummaryPreferenceController extends NotificationPreferenceController { + + private static final String KEY = "bubble_link_pref"; + private static final int SYSTEM_WIDE_ON = 1; + private static final int SYSTEM_WIDE_OFF = 0; + + public BubbleSummaryPreferenceController(Context context, NotificationBackend backend) { + super(context, backend); + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public boolean isAvailable() { + if (!super.isAvailable()) { + return false; + } + if (mAppRow == null && mChannel == null) { + return false; + } + if (Settings.Secure.getInt(mContext.getContentResolver(), + NOTIFICATION_BUBBLES, SYSTEM_WIDE_ON) == SYSTEM_WIDE_OFF) { + return false; + } + if (mChannel != null) { + if (isDefaultChannel()) { + return true; + } else { + return mAppRow == null ? false : mAppRow.allowBubbles; + } + } + return true; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + if (mAppRow != null) { + Bundle args = new Bundle(); + args.putString(AppInfoBase.ARG_PACKAGE_NAME, mAppRow.pkg); + args.putInt(AppInfoBase.ARG_PACKAGE_UID, mAppRow.uid); + + preference.setIntent(new SubSettingLauncher(mContext) + .setDestination(AppBubbleNotificationSettings.class.getName()) + .setArguments(args) + .setSourceMetricsCategory( + SettingsEnums.NOTIFICATION_APP_NOTIFICATION) + .toIntent()); + } + } + + @Override + public CharSequence getSummary() { + boolean canBubble = false; + if (mAppRow != null) { + if (mChannel != null) { + canBubble |= mChannel.canBubble(); + } else { + canBubble |= mAppRow.allowBubbles; + } + } + return mContext.getString(canBubble ? R.string.switch_on_text : R.string.switch_off_text); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java index 99787d824bc..6d13798bd27 100644 --- a/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/BubblePreferenceControllerTest.java @@ -23,6 +23,7 @@ import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -223,6 +224,7 @@ public class BubblePreferenceControllerTest { @Test public void testUpdateState_app() { NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.label = "App!"; appRow.allowBubbles = true; mController.onResume(appRow, null, null, null); @@ -235,6 +237,9 @@ public class BubblePreferenceControllerTest { mController.updateState(pref); assertFalse(pref.isChecked()); + + assertNotNull(pref.getSummary()); + assertTrue(pref.getSummary().toString().contains(appRow.label)); } @Test diff --git a/tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java new file mode 100644 index 00000000000..4bdb2cca72a --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BubbleSummaryNotificationPreferenceControllerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2019 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.notification; + +import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; + +import static com.android.settings.notification.BadgingNotificationPreferenceController.OFF; +import static com.android.settings.notification.BadgingNotificationPreferenceController.ON; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.provider.Settings; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import androidx.preference.Preference; + +@RunWith(RobolectricTestRunner.class) +public class BubbleSummaryNotificationPreferenceControllerTest { + + private Context mContext; + + private BubbleSummaryNotificationPreferenceController mController; + private Preference mPreference; + + private static final String KEY_NOTIFICATION_BUBBLES = "notification_bubbles"; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mController = new BubbleSummaryNotificationPreferenceController(mContext, + KEY_NOTIFICATION_BUBBLES); + mPreference = new Preference(RuntimeEnvironment.application); + } + + @Test + public void display_shouldDisplay() { + assertThat(mPreference.isVisible()).isTrue(); + } + + @Test + public void getSummary() { + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, OFF); + + assertThat(mController.getSummary()).isEqualTo("Off"); + + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, ON); + + assertThat(mController.getSummary()).isEqualTo("On"); + } +} diff --git a/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java new file mode 100644 index 00000000000..5158e82e3b4 --- /dev/null +++ b/tests/robotests/src/com/android/settings/notification/BubbleSummaryPreferenceControllerTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2019 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.notification; + +import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID; +import static android.app.NotificationManager.IMPORTANCE_HIGH; +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES; + +import static junit.framework.TestCase.assertEquals; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.os.UserManager; +import android.provider.Settings; + +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowApplication; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +@RunWith(RobolectricTestRunner.class) +public class BubbleSummaryPreferenceControllerTest { + + private Context mContext; + @Mock + private NotificationBackend mBackend; + + private BubbleSummaryPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + ShadowApplication shadowApplication = ShadowApplication.getInstance(); + mContext = RuntimeEnvironment.application; + mController = spy(new BubbleSummaryPreferenceController(mContext, mBackend)); + } + + @Test + public void testNoCrashIfNoOnResume() { + mController.isAvailable(); + mController.updateState(mock(Preference.class)); + } + + @Test + public void testIsAvailable_notIfAppBlocked() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.banned = true; + mController.onResume(appRow, mock(NotificationChannel.class), null, null); + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_notIfOffGlobally() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + mController.onResume(appRow, channel, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 0); + + assertFalse(mController.isAvailable()); + } + + @Test + public void testIsAvailable_app() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + mController.onResume(appRow, null, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1); + + assertTrue(mController.isAvailable()); + } + + @Test + public void testIsAvailable_defaultChannel() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + NotificationChannel channel = mock(NotificationChannel.class); + when(channel.getImportance()).thenReturn(IMPORTANCE_HIGH); + when(channel.getId()).thenReturn(DEFAULT_CHANNEL_ID); + mController.onResume(appRow, channel, null, null); + Settings.Secure.putInt(mContext.getContentResolver(), NOTIFICATION_BUBBLES, 1); + + assertTrue(mController.isAvailable()); + } + + @Test + public void testUpdateState() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + mController.onResume(appRow, null, null, null); + + Preference pref = new Preference(mContext); + mController.updateState(pref); + assertNotNull(pref.getIntent()); + } + + @Test + public void testGetSummary() { + NotificationBackend.AppRow appRow = new NotificationBackend.AppRow(); + appRow.allowBubbles = true; + mController.onResume(appRow, null, null, null); + + assertEquals("On", mController.getSummary()); + + appRow.allowBubbles = false; + mController.onResume(appRow, null, null, null); + + assertEquals("Off", mController.getSummary()); + } +} From 7ccbf69390a2a1e04d2a3adcacf27714d551629c Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Tue, 26 Mar 2019 19:20:39 -0700 Subject: [PATCH 35/43] Fix dark theme for screen lock settings Test: atest [...]/SetupWizardUtilsTest.java Bug: 36529801 Change-Id: I81581b4f73754ba6b0754c80a8f962e8f7ed6949 --- .../android/settings/SetupWizardUtils.java | 58 ++++++++++++------- .../settings/SetupWizardUtilsTest.java | 25 +++++++- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/com/android/settings/SetupWizardUtils.java b/src/com/android/settings/SetupWizardUtils.java index 166e0655f1d..7a2fe8048a5 100644 --- a/src/com/android/settings/SetupWizardUtils.java +++ b/src/com/android/settings/SetupWizardUtils.java @@ -30,40 +30,56 @@ public class SetupWizardUtils { if (theme == null) { theme = SetupWizardProperties.theme().orElse(""); } + // TODO(yukl): Move to ThemeResolver and add any additional required attributes in + // onApplyThemeResource using Theme overlays if (theme != null) { - switch (theme) { - case ThemeHelper.THEME_GLIF_V3_LIGHT: - return R.style.GlifV3Theme_Light; - case ThemeHelper.THEME_GLIF_V3: - return R.style.GlifV3Theme; - case ThemeHelper.THEME_GLIF_V2_LIGHT: - return R.style.GlifV2Theme_Light; - case ThemeHelper.THEME_GLIF_V2: - return R.style.GlifV2Theme; - case ThemeHelper.THEME_GLIF_LIGHT: - return R.style.GlifTheme_Light; - case ThemeHelper.THEME_GLIF: - return R.style.GlifTheme; + if (WizardManagerHelper.isAnySetupWizard(intent)) { + switch (theme) { + case ThemeHelper.THEME_GLIF_V3_LIGHT: + return R.style.GlifV3Theme_Light; + case ThemeHelper.THEME_GLIF_V3: + return R.style.GlifV3Theme; + case ThemeHelper.THEME_GLIF_V2_LIGHT: + return R.style.GlifV2Theme_Light; + case ThemeHelper.THEME_GLIF_V2: + return R.style.GlifV2Theme; + case ThemeHelper.THEME_GLIF_LIGHT: + return R.style.GlifTheme_Light; + case ThemeHelper.THEME_GLIF: + return R.style.GlifTheme; + } + } else { + switch (theme) { + case ThemeHelper.THEME_GLIF_V3_LIGHT: + case ThemeHelper.THEME_GLIF_V3: + return R.style.GlifV3Theme; + case ThemeHelper.THEME_GLIF_V2_LIGHT: + case ThemeHelper.THEME_GLIF_V2: + return R.style.GlifV2Theme; + case ThemeHelper.THEME_GLIF_LIGHT: + case ThemeHelper.THEME_GLIF: + return R.style.GlifTheme; + } } } - return R.style.GlifTheme_Light; + return R.style.GlifTheme; } public static int getTransparentTheme(Intent intent) { final int suwTheme = getTheme(intent); - int wifiDialogTheme = R.style.GlifV2Theme_Light_Transparent; + int transparentTheme = R.style.GlifV2Theme_Light_Transparent; if (suwTheme == R.style.GlifV3Theme) { - wifiDialogTheme = R.style.GlifV3Theme_Transparent; + transparentTheme = R.style.GlifV3Theme_Transparent; } else if (suwTheme == R.style.GlifV3Theme_Light) { - wifiDialogTheme = R.style.GlifV3Theme_Light_Transparent; + transparentTheme = R.style.GlifV3Theme_Light_Transparent; } else if (suwTheme == R.style.GlifV2Theme) { - wifiDialogTheme = R.style.GlifV2Theme_Transparent; + transparentTheme = R.style.GlifV2Theme_Transparent; } else if (suwTheme == R.style.GlifTheme_Light) { - wifiDialogTheme = R.style.SetupWizardTheme_Light_Transparent; + transparentTheme = R.style.SetupWizardTheme_Light_Transparent; } else if (suwTheme == R.style.GlifTheme) { - wifiDialogTheme = R.style.SetupWizardTheme_Transparent; + transparentTheme = R.style.SetupWizardTheme_Transparent; } - return wifiDialogTheme; + return transparentTheme; } public static void copySetupExtras(Intent fromIntent, Intent toIntent) { diff --git a/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java b/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java index 4fe8fa8a466..b965d788338 100644 --- a/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java +++ b/tests/robotests/src/com/android/settings/SetupWizardUtilsTest.java @@ -31,6 +31,9 @@ import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class SetupWizardUtilsTest { + private static final String EXTRA_IS_SETUP_FLOW = "isSetupFlow"; + private static final String EXTRA_IS_FIRST_RUN = "firstRun"; + @Test public void testCopySetupExtras() { Intent fromIntent = new Intent(); @@ -48,7 +51,7 @@ public class SetupWizardUtilsTest { @Test public void testGetTheme_withIntentExtra_shouldReturnExtraTheme() { SetupWizardProperties.theme(ThemeHelper.THEME_GLIF); - Intent intent = new Intent(); + Intent intent = createSetupWizardIntent(); intent.putExtra(WizardManagerHelper.EXTRA_THEME, ThemeHelper.THEME_GLIF_V2); assertThat(SetupWizardUtils.getTheme(intent)).isEqualTo(R.style.GlifV2Theme); @@ -57,7 +60,7 @@ public class SetupWizardUtilsTest { @Test public void testGetTheme_withEmptyIntent_shouldReturnSystemProperty() { SetupWizardProperties.theme(ThemeHelper.THEME_GLIF_V2_LIGHT); - Intent intent = new Intent(); + Intent intent = createSetupWizardIntent(); assertThat(SetupWizardUtils.getTheme(intent)).isEqualTo(R.style.GlifV2Theme_Light); } @@ -65,10 +68,26 @@ public class SetupWizardUtilsTest { @Test public void testGetTheme_glifV3Light_shouldReturnThemeResource() { SetupWizardProperties.theme(ThemeHelper.THEME_GLIF_V3_LIGHT); - Intent intent = new Intent(); + Intent intent = createSetupWizardIntent(); assertThat(SetupWizardUtils.getTheme(intent)).isEqualTo(R.style.GlifV3Theme_Light); assertThat(SetupWizardUtils.getTransparentTheme(intent)) .isEqualTo(R.style.GlifV3Theme_Light_Transparent); } + + @Test + public void testGetTheme_nonSuw_shouldReturnDayNightTheme() { + SetupWizardProperties.theme(ThemeHelper.THEME_GLIF_V3_LIGHT); + Intent intent = new Intent(); + + assertThat(SetupWizardUtils.getTheme(intent)).isEqualTo(R.style.GlifV3Theme); + assertThat(SetupWizardUtils.getTransparentTheme(intent)) + .isEqualTo(R.style.GlifV3Theme_Transparent); + } + + private Intent createSetupWizardIntent() { + return new Intent() + .putExtra(EXTRA_IS_SETUP_FLOW, true) + .putExtra(EXTRA_IS_FIRST_RUN, true); + } } From b38e333535b482b0e1ca14095ae1726829297fbd Mon Sep 17 00:00:00 2001 From: lindatseng Date: Thu, 28 Mar 2019 17:24:30 -0700 Subject: [PATCH 36/43] Update Model pref subtext to remove redundant prefix Remove "Model: " prefix from the description of Model pref in About phone > Model & hardware > Model Test: Visual veridication Fixes: 128622275 Change-Id: I8fb077809e13fded0370271e899163a7f4103f98 --- .../hardwareinfo/DeviceModelPreferenceController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/android/settings/deviceinfo/hardwareinfo/DeviceModelPreferenceController.java b/src/com/android/settings/deviceinfo/hardwareinfo/DeviceModelPreferenceController.java index 42c7b0104e7..fbd1e9e2515 100644 --- a/src/com/android/settings/deviceinfo/hardwareinfo/DeviceModelPreferenceController.java +++ b/src/com/android/settings/deviceinfo/hardwareinfo/DeviceModelPreferenceController.java @@ -39,4 +39,9 @@ public class DeviceModelPreferenceController extends HardwareInfoPreferenceContr public boolean isSliceable() { return true; } + + @Override + public CharSequence getSummary() { + return HardwareInfoPreferenceController.getDeviceModel(); + } } From 03b8cd76ac526e551c5218e074c63cc703b489d4 Mon Sep 17 00:00:00 2001 From: cosmohsieh Date: Tue, 26 Mar 2019 15:22:08 +0800 Subject: [PATCH 37/43] [Network Connection] Correct canceling dialog behavior Use setCancelable() rather than setCanceledOnTouchOutside() for better code smell and consisting with general not canceling behavior. The back key and touching outside will not cancel this dialog for better UX. Bug: 128877712 Test: atest NetworkRequestDialogFragmentTest Change-Id: I19bc5637a7307610e34db15d54c1d80d5b7e379b --- .../android/settings/wifi/NetworkRequestDialogFragment.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java index e9057e6972d..2a7ac17ab5b 100644 --- a/src/com/android/settings/wifi/NetworkRequestDialogFragment.java +++ b/src/com/android/settings/wifi/NetworkRequestDialogFragment.java @@ -138,9 +138,9 @@ public class NetworkRequestDialogFragment extends InstrumentedDialogFragment imp .setOnItemClickListener( (parent, view, position, id) -> this.onClick(dialog, position)); - // Don't dismiss dialog when touching outside. User report it is easy to touch outside. - // This causes dialog to close. Which is concerned as a bad UX (b/128877712). - dialog.setCanceledOnTouchOutside(false); + // Don't dismiss dialog when touching outside. User reports it is easy to touch outside. + // This causes dialog to close. + setCancelable(false); dialog.setOnShowListener((dialogInterface) -> { // Replace NeutralButton onClickListener to avoid closing dialog From dae6c695ca76f400c5f7c3738f5c7bce9e1bd33d Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Thu, 28 Mar 2019 15:39:03 +0800 Subject: [PATCH 38/43] Hide Wi-Fi QR code scan button for unsupported Wi-Fi networks Wi-Fi QR code scan button only shows for the Wi-Fi network which supports Wi-Fi Easy Connect or ZXing Wi-Fi config format. Bug: 128847959 Test: manual Change-Id: If6460ce88748e7b06893bf62060418a8727d3134 --- .../settings/wifi/WifiConfigController.java | 18 +++++ .../settings/wifi/dpp/WifiDppUtils.java | 66 +++++++++++++------ 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java index 0887fc5532c..ac11510761c 100644 --- a/src/com/android/settings/wifi/WifiConfigController.java +++ b/src/com/android/settings/wifi/WifiConfigController.java @@ -55,6 +55,7 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; +import android.widget.ImageButton; import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; @@ -64,6 +65,7 @@ import androidx.annotation.VisibleForTesting; import com.android.settings.ProxySelector; import com.android.settings.R; import com.android.settings.wifi.details.WifiPrivacyPreferenceController; +import com.android.settings.wifi.dpp.WifiDppUtils; import com.android.settingslib.Utils; import com.android.settingslib.utils.ThreadUtils; import com.android.settingslib.wifi.AccessPoint; @@ -129,6 +131,8 @@ public class WifiConfigController implements TextWatcher, @VisibleForTesting int mAccessPointSecurity; private TextView mPasswordView; + private ImageButton mSsidScanButton; + private ImageButton mPasswordScanButton; private String mUnspecifiedCertString; private String mMultipleCertSetString; @@ -239,6 +243,8 @@ public class WifiConfigController implements TextWatcher, mDoNotValidateEapServerString = mContext.getString(R.string.wifi_do_not_validate_eap_server); + mSsidScanButton = (ImageButton) mView.findViewById(R.id.ssid_scanner_button); + mPasswordScanButton = (ImageButton) mView.findViewById(R.id.password_scanner_button); mDialogContainer = mView.findViewById(R.id.dialog_scrollview); mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings); mIpSettingsSpinner.setOnItemSelectedListener(this); @@ -264,6 +270,7 @@ public class WifiConfigController implements TextWatcher, if (mAccessPoint == null) { // new network configureSecuritySpinner(); mConfigUi.setSubmitButton(res.getString(R.string.wifi_save)); + mPasswordScanButton.setVisibility(View.GONE); } else { mConfigUi.setTitle(mAccessPoint.getTitle()); @@ -408,6 +415,11 @@ public class WifiConfigController implements TextWatcher, mConfigUi.setForgetButton(res.getString(R.string.wifi_forget)); } } + + if (!WifiDppUtils.isSupportEnrolleeQrCodeScanner(mContext, mAccessPointSecurity)) { + mPasswordScanButton.setVisibility(View.GONE); + } + mSsidScanButton.setVisibility(View.GONE); } if (!isSplitSystemUser()) { @@ -1444,6 +1456,12 @@ public class WifiConfigController implements TextWatcher, // Convert menu position to actual Wi-Fi security type mAccessPointSecurity = mSecurityInPosition[position]; showSecurityFields(); + + if (WifiDppUtils.isSupportEnrolleeQrCodeScanner(mContext, mAccessPointSecurity)) { + mSsidScanButton.setVisibility(View.VISIBLE); + } else { + mSsidScanButton.setVisibility(View.GONE); + } } else if (parent == mEapMethodSpinner || parent == mEapCaCertSpinner) { showSecurityFields(); } else if (parent == mPhase2Spinner diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java index 6c6444c7d9d..42e88a57b02 100644 --- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java +++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java @@ -323,31 +323,36 @@ public class WifiDppUtils { } } + /** + * Checks if QR code scanner supports to config other devices with the Wi-Fi network + * + * @param context The context to use for {@link WifiManager#isEasyConnectSupported()} + * @param accessPoint The {@link AccessPoint} of the Wi-Fi network + */ public static boolean isSupportConfiguratorQrCodeScanner(Context context, AccessPoint accessPoint) { - if (!isWifiDppEnabled(context)) { - return false; - } - - // DPP 1.0 only supports SAE and PSK. - final int security = accessPoint.getSecurity(); - if (security == AccessPoint.SECURITY_SAE || security == AccessPoint.SECURITY_PSK) { - return true; - } - - return false; + return isSupportWifiDpp(context, accessPoint.getSecurity()); } + /** + * Checks if QR code generator supports to config other devices with the Wi-Fi network + * + * @param accessPoint The {@link AccessPoint} of the Wi-Fi network + */ public static boolean isSupportConfiguratorQrCodeGenerator(AccessPoint accessPoint) { - // QR code generator produces QR code with ZXing's Wi-Fi network config format, - // it supports PSK and WEP and non security - final int security = accessPoint.getSecurity(); - if (security == AccessPoint.SECURITY_PSK || security == AccessPoint.SECURITY_WEP || - security == AccessPoint.SECURITY_NONE) { - return true; - } + return isSupportZxing(accessPoint.getSecurity()); + } - return false; + /** + * Checks if this device supports to be configured by the Wi-Fi network of the security + * + * @param context The context to use for {@link WifiManager#isEasyConnectSupported()} + * @param accesspointSecurity The security constants defined in {@link AccessPoint} + */ + public static boolean isSupportEnrolleeQrCodeScanner(Context context, + int accesspointSecurity) { + return isSupportWifiDpp(context, accesspointSecurity) || + isSupportZxing(accesspointSecurity); } private static boolean isSupportHotspotConfiguratorQrCodeGenerator( @@ -358,4 +363,27 @@ public class WifiDppUtils { return wifiConfiguration.allowedKeyManagement.get(KeyMgmt.WPA2_PSK) || wifiConfiguration.allowedKeyManagement.get(KeyMgmt.NONE); } + + private static boolean isSupportWifiDpp(Context context, int accesspointSecurity) { + if (!isWifiDppEnabled(context)) { + return false; + } + + // DPP 1.0 only supports SAE and PSK. + if (accesspointSecurity == AccessPoint.SECURITY_SAE || + accesspointSecurity == AccessPoint.SECURITY_PSK) { + return true; + } + return false; + } + + // TODO (b/124131581 b/129396816): TO support WPA3 securities (SAE & OWE), change here at first + private static boolean isSupportZxing(int accesspointSecurity) { + if (accesspointSecurity == AccessPoint.SECURITY_PSK || + accesspointSecurity == AccessPoint.SECURITY_WEP || + accesspointSecurity == AccessPoint.SECURITY_NONE) { + return true; + } + return false; + } } From 352ca99541ab335fea050ba142c5a8c3926185f8 Mon Sep 17 00:00:00 2001 From: Yanting Yang Date: Fri, 29 Mar 2019 19:42:37 +0800 Subject: [PATCH 39/43] Fix talkback issue on collapsed condition card When condition card is collapsed, there are only showing icons. Accessibility can't speak meaningful information after tap it. Use existed condition expend string to assign content description. Fixes:128897141 Test: manual Change-Id: I49bc1ee8af424e90aac6555829c545914355862b --- res/layout/homepage_condition_header.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/res/layout/homepage_condition_header.xml b/res/layout/homepage_condition_header.xml index 5460be95adc..30b903348d3 100644 --- a/res/layout/homepage_condition_header.xml +++ b/res/layout/homepage_condition_header.xml @@ -19,6 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" + android:contentDescription="@string/condition_expand_show" style="@style/ContextualCardStyle"> Date: Fri, 8 Mar 2019 23:51:04 +0000 Subject: [PATCH 40/43] Disable "Learn more" button for unknown apps disabled by admin on secondary profiles. Test: packages/apps/Settings/tests/robotests/src/com/android/settings/applications/appinfo/ExternalSourcesDetailsTest.java Test: packages/apps/Settings/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelperTest.java Fixes:118881180 Change-Id: I8f6dbd9decba331fbe0a3495a2989f570f2afa67 --- .../ActionDisabledByAdminDialogHelper.java | 28 ++++++++++------ ...ActionDisabledByAdminDialogHelperTest.java | 32 ++++++++++++++++++- .../testutils/shadow/ShadowUserManager.java | 15 +++++++++ 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java index 5bdf5873cf5..5599a94a212 100644 --- a/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java +++ b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java @@ -55,7 +55,7 @@ import java.util.Objects; public class ActionDisabledByAdminDialogHelper { private static final String TAG = ActionDisabledByAdminDialogHelper.class.getName(); - private EnforcedAdmin mEnforcedAdmin; + @VisibleForTesting EnforcedAdmin mEnforcedAdmin; private ViewGroup mDialogView; private String mRestriction = null; private Activity mActivity; @@ -80,20 +80,28 @@ public class ActionDisabledByAdminDialogHelper { EnforcedAdmin enforcedAdmin) { mEnforcedAdmin = enforcedAdmin; mRestriction = restriction; - final AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); mDialogView = (ViewGroup) LayoutInflater.from(builder.getContext()).inflate( R.layout.admin_support_details_dialog, null); initializeDialogViews(mDialogView, mEnforcedAdmin.component, getEnforcementAdminUserId(), mRestriction); - return builder - .setPositiveButton(R.string.okay, null) - .setNeutralButton(R.string.learn_more, - (dialog, which) -> { - showAdminPolicies(mEnforcedAdmin, mActivity); - mActivity.finish(); - }) - .setView(mDialogView); + builder.setPositiveButton(R.string.okay, null).setView(mDialogView); + maybeSetLearnMoreButton(builder); + return builder; + } + + @VisibleForTesting + void maybeSetLearnMoreButton(AlertDialog.Builder builder) { + // The "Learn more" button appears only if the restriction is enforced by an admin in the + // same profile group. Otherwise the admin package and its policies are not accessible to + // the current user. + final UserManager um = UserManager.get(mActivity.getApplicationContext()); + if (um.isSameProfileGroup(getEnforcementAdminUserId(mEnforcedAdmin), um.getUserHandle())) { + builder.setNeutralButton(R.string.learn_more, (dialog, which) -> { + showAdminPolicies(mEnforcedAdmin, mActivity); + mActivity.finish(); + }); + } } public void updateDialog(String restriction, EnforcedAdmin admin) { diff --git a/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelperTest.java b/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelperTest.java index c01ef3fa56e..ff3a36f2574 100644 --- a/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelperTest.java +++ b/tests/robotests/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelperTest.java @@ -20,6 +20,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.app.Activity; import android.app.admin.DevicePolicyManager; @@ -33,6 +38,8 @@ import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.appcompat.app.AlertDialog; + import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.applications.specialaccess.deviceadmin.DeviceAdminAdd; @@ -179,5 +186,28 @@ public class ActionDisabledByAdminDialogHelperTest { mHelper.setAdminSupportDetails(mActivity, null, admin); assertNull(admin.component); } -} + @Test + public void testMaybeSetLearnMoreButton() { + final UserManager userManager = RuntimeEnvironment.application.getSystemService( + UserManager.class); + final ShadowUserManager userManagerShadow = Shadow.extract(userManager); + final ComponentName component = new ComponentName("some.package.name", + "some.package.name.SomeClass"); + mHelper.mEnforcedAdmin = new EnforcedAdmin(component, UserHandle.of(123)); + + // Set up for shadow call. + userManagerShadow.getSameProfileGroupIds().put(123, 0); + + // Test that the button is shown when user IDs are in the same profile group + AlertDialog.Builder builder = mock(AlertDialog.Builder.class); + mHelper.maybeSetLearnMoreButton(builder); + verify(builder).setNeutralButton(anyInt(), any()); + + // Test that the button is not shown when user IDs are not in the same profile group + userManagerShadow.getSameProfileGroupIds().clear(); + builder = mock(AlertDialog.Builder.class); + mHelper.maybeSetLearnMoreButton(builder); + verify(builder, never()).setNeutralButton(anyInt(), any()); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java index bceba3c9864..a85fe107b3c 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUserManager.java @@ -22,6 +22,8 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.UserManager.EnforcingUser; +import com.google.android.collect.Maps; + import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -48,6 +50,7 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager private boolean mIsQuietModeEnabled = false; private int[] profileIdsForUser = new int[0]; private boolean mUserSwitchEnabled; + private final Map mSameProfileGroupIds = Maps.newHashMap(); public void addProfile(UserInfo userInfo) { mUserProfileInfos.add(userInfo); @@ -138,6 +141,18 @@ public class ShadowUserManager extends org.robolectric.shadows.ShadowUserManager return sIsSupportsMultipleUsers; } + @Implementation + protected boolean isSameProfileGroup(@UserIdInt int userId, int otherUserId) { + return mSameProfileGroupIds.containsKey(userId) + && mSameProfileGroupIds.get(userId) == otherUserId + || mSameProfileGroupIds.containsKey(otherUserId) + && mSameProfileGroupIds.get(otherUserId) == userId; + } + + public Map getSameProfileGroupIds() { + return mSameProfileGroupIds; + } + public void setSupportsMultipleUsers(boolean supports) { sIsSupportsMultipleUsers = supports; } From 7f78005f0a84aa9e809335fc6d7662482708ceb4 Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Fri, 29 Mar 2019 22:36:02 +0800 Subject: [PATCH 41/43] Clean up test cases related to long press dismissal Bug: 126214056 Test: robotests Change-Id: I1d0d21453e5c7b2c12ea6c0fa2c385d3bba0e04e --- .../slices/SliceContextualCardRendererTest.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java index a53ade2d4fd..706f2386372 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/slices/SliceContextualCardRendererTest.java @@ -142,12 +142,11 @@ public class SliceContextualCardRendererTest { @Test public void viewClick_keepCard_shouldFlipBackToSlice() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - final View card = viewHolder.itemView.findViewById(R.id.slice_view); final Button btnKeep = viewHolder.itemView.findViewById(R.id.keep); final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.view_flipper); mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); + viewFlipper.setDisplayedChild(1); - card.performLongClick(); btnKeep.performClick(); assertThat(viewFlipper.getCurrentView()).isInstanceOf(SliceView.class); @@ -156,11 +155,10 @@ public class SliceContextualCardRendererTest { @Test public void viewClick_keepCard_shouldRemoveViewHolderFromSet() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - final View card = viewHolder.itemView.findViewById(R.id.slice_view); final Button btnKeep = viewHolder.itemView.findViewById(R.id.keep); mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); + mRenderer.mFlippedCardSet.add(viewHolder); - card.performLongClick(); btnKeep.performClick(); assertThat(mRenderer.mFlippedCardSet).doesNotContain(viewHolder); @@ -169,14 +167,13 @@ public class SliceContextualCardRendererTest { @Test public void viewClick_removeCard_shouldRemoveViewHolderFromSet() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - final View card = viewHolder.itemView.findViewById(R.id.slice_view); final Button btnRemove = viewHolder.itemView.findViewById(R.id.remove); final ContextualCard contextualCard = buildContextualCard(TEST_SLICE_URI); mRenderer.bindView(viewHolder, contextualCard); doReturn(mController).when(mControllerRendererPool).getController(mActivity, ContextualCard.CardType.SLICE); + mRenderer.mFlippedCardSet.add(viewHolder); - card.performLongClick(); btnRemove.performClick(); assertThat(mRenderer.mFlippedCardSet).doesNotContain(viewHolder); @@ -185,7 +182,6 @@ public class SliceContextualCardRendererTest { @Test public void viewClick_removeCard_sliceLiveDataShouldRemoveObservers() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - final View card = viewHolder.itemView.findViewById(R.id.slice_view); final Button btnRemove = viewHolder.itemView.findViewById(R.id.remove); final ContextualCard contextualCard = buildContextualCard(TEST_SLICE_URI); mRenderer.mSliceLiveDataMap.put(TEST_SLICE_URI, mSliceLiveData); @@ -193,7 +189,6 @@ public class SliceContextualCardRendererTest { doReturn(mController).when(mControllerRendererPool).getController(mActivity, ContextualCard.CardType.SLICE); - card.performLongClick(); btnRemove.performClick(); assertThat(mRenderer.mSliceLiveDataMap.get(TEST_SLICE_URI).hasObservers()).isFalse(); @@ -202,11 +197,11 @@ public class SliceContextualCardRendererTest { @Test public void onStop_cardIsFlipped_shouldFlipBack() { final RecyclerView.ViewHolder viewHolder = getSliceViewHolder(); - final View card = viewHolder.itemView.findViewById(R.id.slice_view); final ViewFlipper viewFlipper = viewHolder.itemView.findViewById(R.id.view_flipper); mRenderer.bindView(viewHolder, buildContextualCard(TEST_SLICE_URI)); + viewFlipper.setDisplayedChild(1); + mRenderer.mFlippedCardSet.add(viewHolder); - card.performLongClick(); mRenderer.onStop(); assertThat(viewFlipper.getCurrentView()).isInstanceOf(SliceView.class); From e7cad1839418b1dcb3bb3728b93ec2e8296810b3 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Fri, 29 Mar 2019 10:58:54 -0700 Subject: [PATCH 42/43] Add log about ChooseLockGeneric refusing to start Test: Manual Bug: 129445834 Change-Id: I4fd034a3c3d1c004144d4b49c0ce14e7aa89fcba --- src/com/android/settings/password/ChooseLockGeneric.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/android/settings/password/ChooseLockGeneric.java b/src/com/android/settings/password/ChooseLockGeneric.java index 4a758be809a..580c7ba334b 100644 --- a/src/com/android/settings/password/ChooseLockGeneric.java +++ b/src/com/android/settings/password/ChooseLockGeneric.java @@ -183,6 +183,7 @@ public class ChooseLockGeneric extends SettingsActivity { super.onCreate(savedInstanceState); final Activity activity = getActivity(); if (!Utils.isDeviceProvisioned(activity) && !canRunBeforeDeviceProvisioned()) { + Log.i(TAG, "Refusing to start because device is not provisioned"); activity.finish(); return; } From d9e3a34e7c1697fc823615b6fc3f26e85c87a1c0 Mon Sep 17 00:00:00 2001 From: Mill Chen Date: Sat, 30 Mar 2019 01:55:23 +0000 Subject: [PATCH 43/43] Revert "Use explicit intent to broadcast grayscale state changed" This reverts commit 2ef3dacad855a3493f630b208b323fb2f6b29fa9. Reason for revert: use Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND to implement Change-Id: I39754866d1c18eb6c024dcc0555e763a82b7a14f --- .../GrayscaleConditionController.java | 20 +++++-------------- .../GrayscaleConditionControllerTest.java | 13 +----------- 2 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java b/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java index 61b24df6cba..341e0612568 100644 --- a/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java +++ b/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionController.java @@ -22,27 +22,21 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.hardware.display.ColorDisplayManager; import android.util.Log; -import androidx.annotation.VisibleForTesting; - import com.android.settings.R; import com.android.settings.homepage.contextualcards.ContextualCard; import java.net.URISyntaxException; -import java.util.List; import java.util.Objects; public class GrayscaleConditionController implements ConditionalCardController { static final int ID = Objects.hash("GrayscaleConditionController"); - @VisibleForTesting - static final String ACTION_GRAYSCALE_CHANGED = "android.settings.action.GRAYSCALE_CHANGED"; - private static final String TAG = "GrayscaleCondition"; + private static final String ACTION_GRAYSCALE_CHANGED = + "android.settings.action.GRAYSCALE_CHANGED"; private static final IntentFilter GRAYSCALE_CHANGED_FILTER = new IntentFilter( ACTION_GRAYSCALE_CHANGED); @@ -119,13 +113,9 @@ public class GrayscaleConditionController implements ConditionalCardController { } private void sendBroadcast() { - final PackageManager pm = mAppContext.getPackageManager(); - final Intent intent = new Intent(ACTION_GRAYSCALE_CHANGED); - final List receivers = pm.queryBroadcastReceivers(intent, 0 /* flags */); - for (ResolveInfo receiver : receivers) { - intent.setPackage(receiver.activityInfo.packageName); - mAppContext.sendBroadcast(intent); - } + final Intent intent = new Intent(); + intent.setAction(ACTION_GRAYSCALE_CHANGED); + mAppContext.sendBroadcast(intent, Manifest.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS); } public class Receiver extends BroadcastReceiver { diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java index 11e76b53295..2fe4697f099 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/conditional/GrayscaleConditionControllerTest.java @@ -25,8 +25,6 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ResolveInfo; import android.hardware.display.ColorDisplayManager; import org.junit.Before; @@ -36,9 +34,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; -import org.robolectric.Shadows; import org.robolectric.annotation.Config; -import org.robolectric.shadows.ShadowPackageManager; @RunWith(RobolectricTestRunner.class) public class GrayscaleConditionControllerTest { @@ -49,7 +45,6 @@ public class GrayscaleConditionControllerTest { private ColorDisplayManager mColorDisplayManager; private Context mContext; private GrayscaleConditionController mController; - private ShadowPackageManager mPackageManager; @Before public void setUp() { @@ -58,7 +53,6 @@ public class GrayscaleConditionControllerTest { mColorDisplayManager = spy(mContext.getSystemService(ColorDisplayManager.class)); doReturn(mColorDisplayManager).when(mContext).getSystemService(ColorDisplayManager.class); mController = new GrayscaleConditionController(mContext, mConditionManager); - mPackageManager = Shadows.shadowOf(mContext.getPackageManager()); } @Test @@ -91,13 +85,8 @@ public class GrayscaleConditionControllerTest { @Test public void onActionClick_shouldSendBroadcast() { - final Intent intent = new Intent(GrayscaleConditionController.ACTION_GRAYSCALE_CHANGED); - final ResolveInfo info = new ResolveInfo(); - info.activityInfo = new ActivityInfo(); - mPackageManager.addResolveInfoForIntent(intent, info); - mController.onActionClick(); - verify(mContext).sendBroadcast(any(Intent.class)); + verify(mContext).sendBroadcast(any(Intent.class), any(String.class)); } }