diff --git a/AndroidManifest.xml b/AndroidManifest.xml index bbb8eea9700..6c0dbff5b06 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -4179,11 +4179,6 @@ - - - - - @@ -4196,6 +4191,21 @@ android:value="true" /> + + + + + + + + + + launchPairingDetail()); } @Override protected void refresh() { + updateButtonPreferenceTitle(mPreference); mPreference.setVisible(getButtonPreferenceVisibility(mCachedDevice)); + + } + + private void updateButtonPreferenceTitle(ButtonPreference preference) { + final int side = mCachedDevice.getDeviceSide(); + final int stringRes = (side == HearingAidProfile.DeviceSide.SIDE_LEFT) + ? R.string.bluetooth_pair_right_ear_button + : R.string.bluetooth_pair_left_ear_button; + + preference.setTitle(stringRes); } private boolean getButtonPreferenceVisibility(CachedBluetoothDevice cachedDevice) { diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index c6f64d602ca..0f6db8a9291 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -77,6 +77,7 @@ import com.android.settings.bluetooth.BluetoothFindBroadcastsFragment; import com.android.settings.bluetooth.BluetoothPairingDetail; import com.android.settings.bugreporthandler.BugReportHandlerPicker; import com.android.settings.connecteddevice.AdvancedConnectedDeviceDashboardFragment; +import com.android.settings.connecteddevice.NfcAndPaymentFragment; import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment; import com.android.settings.connecteddevice.PreviouslyConnectedDeviceDashboardFragment; import com.android.settings.connecteddevice.usb.UsbDetailsFragment; @@ -349,7 +350,8 @@ public class SettingsGateway { MobileNetworkSettings.class.getName(), AppLocaleDetails.class.getName(), TurnScreenOnSettings.class.getName(), - TurnScreenOnDetails.class.getName() + TurnScreenOnDetails.class.getName(), + NfcAndPaymentFragment.class.getName() }; public static final String[] SETTINGS_FOR_RESTRICTED = { diff --git a/src/com/android/settings/dream/DreamAdapter.java b/src/com/android/settings/dream/DreamAdapter.java index cfee12e0f42..b81d6b6d679 100644 --- a/src/com/android/settings/dream/DreamAdapter.java +++ b/src/com/android/settings/dream/DreamAdapter.java @@ -21,6 +21,7 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.graphics.drawable.VectorDrawable; import android.text.TextUtils; +import android.util.SparseIntArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -41,10 +42,9 @@ import java.util.List; */ public class DreamAdapter extends RecyclerView.Adapter { private final List mItemList; - @LayoutRes - private final int mLayoutRes; private int mLastSelectedPos = -1; private boolean mEnabled = true; + private SparseIntArray mLayouts = new SparseIntArray(); /** * View holder for each {@link IDreamItem}. @@ -83,16 +83,6 @@ public class DreamAdapter extends RecyclerView.Adapter mSummaryView.setVisibility(View.VISIBLE); } - final Drawable previewImage = item.getPreviewImage(); - if (previewImage != null) { - mPreviewView.setImageDrawable(previewImage); - mPreviewView.setClipToOutline(true); - mPreviewPlaceholderView.setVisibility(View.GONE); - } else { - mPreviewView.setImageDrawable(null); - mPreviewPlaceholderView.setVisibility(View.VISIBLE); - } - final Drawable icon = item.isActive() ? mContext.getDrawable(R.drawable.ic_dream_check_circle) : item.getIcon().mutate(); @@ -122,12 +112,24 @@ public class DreamAdapter extends RecyclerView.Adapter itemView.setClickable(true); } - mCustomizeButton.setOnClickListener(v -> item.onCustomizeClicked()); - mCustomizeButton.setVisibility( - item.allowCustomization() && mEnabled ? View.VISIBLE : View.GONE); - // This must be called AFTER itemView.setSelected above, in order to keep the - // customize button in an unselected state. - mCustomizeButton.setSelected(false); + if (item.viewType() != DreamItemViewTypes.NO_DREAM_ITEM) { + final Drawable previewImage = item.getPreviewImage(); + if (previewImage != null) { + mPreviewView.setImageDrawable(previewImage); + mPreviewView.setClipToOutline(true); + mPreviewPlaceholderView.setVisibility(View.GONE); + } else { + mPreviewView.setImageDrawable(null); + mPreviewPlaceholderView.setVisibility(View.VISIBLE); + } + + mCustomizeButton.setOnClickListener(v -> item.onCustomizeClicked()); + mCustomizeButton.setVisibility( + item.allowCustomization() && mEnabled ? View.VISIBLE : View.GONE); + // This must be called AFTER itemView.setSelected above, in order to keep the + // customize button in an unselected state. + mCustomizeButton.setSelected(false); + } setEnabledStateOnViews(itemView, mEnabled); } @@ -149,16 +151,22 @@ public class DreamAdapter extends RecyclerView.Adapter } } + public DreamAdapter(SparseIntArray layouts, List itemList) { + mItemList = itemList; + mLayouts = layouts; + } + public DreamAdapter(@LayoutRes int layoutRes, List itemList) { mItemList = itemList; - mLayoutRes = layoutRes; + mLayouts.append(DreamItemViewTypes.DREAM_ITEM, layoutRes); } @NonNull @Override - public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, + @DreamItemViewTypes.ViewType int viewType) { View view = LayoutInflater.from(viewGroup.getContext()) - .inflate(mLayoutRes, viewGroup, false); + .inflate(mLayouts.get(viewType), viewGroup, false); return new DreamViewHolder(view, viewGroup.getContext()); } @@ -167,6 +175,11 @@ public class DreamAdapter extends RecyclerView.Adapter ((DreamViewHolder) viewHolder).bindView(mItemList.get(i), i); } + @Override + public @DreamItemViewTypes.ViewType int getItemViewType(int position) { + return mItemList.get(position).viewType(); + } + @Override public int getItemCount() { return mItemList.size(); diff --git a/src/com/android/settings/dream/DreamItemViewTypes.java b/src/com/android/settings/dream/DreamItemViewTypes.java new file mode 100644 index 00000000000..b7202429769 --- /dev/null +++ b/src/com/android/settings/dream/DreamItemViewTypes.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.dream; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Class representing a dream item view types. + */ +public final class DreamItemViewTypes { + + /** + * The default dream item layout + */ + public static final int DREAM_ITEM = 0; + + /** + * The dream item layout indicating no dream item selected. + */ + public static final int NO_DREAM_ITEM = 1; + + + @Retention(RetentionPolicy.SOURCE) + @IntDef({DreamItemViewTypes.DREAM_ITEM, DreamItemViewTypes.NO_DREAM_ITEM}) + public @interface ViewType {} +} diff --git a/src/com/android/settings/dream/IDreamItem.java b/src/com/android/settings/dream/IDreamItem.java index 49c82bec42a..911a3cf0bd2 100644 --- a/src/com/android/settings/dream/IDreamItem.java +++ b/src/com/android/settings/dream/IDreamItem.java @@ -67,4 +67,11 @@ public interface IDreamItem { default boolean allowCustomization() { return false; } + + /** + * Returns whether or not this item is the no screensaver item. + */ + default @DreamItemViewTypes.ViewType int viewType() { + return DreamItemViewTypes.DREAM_ITEM; + } } diff --git a/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt index 77c0f685bd2..556ea579a83 100644 --- a/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt +++ b/src/com/android/settings/spa/app/specialaccess/InstallUnknownApps.kt @@ -22,6 +22,7 @@ import android.app.AppOpsManager.MODE_DEFAULT import android.app.AppOpsManager.OP_REQUEST_INSTALL_PACKAGES import android.content.Context import android.content.pm.ApplicationInfo +import android.os.UserManager import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState import com.android.settings.R @@ -49,6 +50,10 @@ class InstallUnknownAppsListModel(private val context: Context) : override val pageTitleResId = R.string.install_other_apps override val switchTitleResId = R.string.external_source_switch_title override val footerResId = R.string.install_all_warning + override val switchRestrictionKeys = listOf( + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, + ) override fun transformItem(app: ApplicationInfo) = InstallUnknownAppsRecord( app = app, diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java index cfa6d41e0d7..d01e3ac4d58 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsPairOtherControllerTest.java @@ -56,7 +56,7 @@ public class BluetoothDetailsPairOtherControllerTest extends BluetoothDetailsCon } @Test - public void init_leftSideDevice_expectedTitle() { + public void init_leftSideDevice_rightSideButtonTitle() { when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidProfile.DeviceSide.SIDE_LEFT); mController.init(mScreen); @@ -66,7 +66,7 @@ public class BluetoothDetailsPairOtherControllerTest extends BluetoothDetailsCon } @Test - public void init_rightSideDevice_expectedTitle() { + public void init_rightSideDevice_leftSideButtonTitle() { when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidProfile.DeviceSide.SIDE_RIGHT); mController.init(mScreen); @@ -118,4 +118,15 @@ public class BluetoothDetailsPairOtherControllerTest extends BluetoothDetailsCon assertThat(mController.isAvailable()).isTrue(); } + + @Test + public void refresh_leftSideDevice_leftSideButtonTitle() { + when(mCachedDevice.getDeviceSide()).thenReturn(HearingAidProfile.DeviceSide.SIDE_RIGHT); + mController.init(mScreen); + + mController.refresh(); + + assertThat(mPreference.getTitle().toString()).isEqualTo( + mContext.getString(R.string.bluetooth_pair_left_ear_button)); + } } diff --git a/tests/unit/src/com/android/settings/applications/AppStateAppBatteryUsageBridgeTest.java b/tests/unit/src/com/android/settings/applications/AppStateAppBatteryUsageBridgeTest.java new file mode 100644 index 00000000000..c49b4cd8d2f --- /dev/null +++ b/tests/unit/src/com/android/settings/applications/AppStateAppBatteryUsageBridgeTest.java @@ -0,0 +1,78 @@ +package com.android.settings.applications; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.settingslib.applications.ApplicationsState.AppEntry; +import com.android.settingslib.fuelgauge.PowerAllowlistBackend; + +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; + +@RunWith(AndroidJUnit4.class) +public final class AppStateAppBatteryUsageBridgeTest { + private static final String TEST_PACKAGE_1 = "com.example.test.pkg1"; + private static final String TEST_PACKAGE_2 = "com.example.test.pkg2"; + private static final int UID_1 = 12345; + private static final int UID_2 = 7654321; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private Context mContext; + @Mock + private AppOpsManager mAppOpsManager; + @Mock + private PowerAllowlistBackend mPowerAllowlistBackend; + + @Before + public void initMocks() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void updateExtraInfo_updatesRestricted() { + when(mPowerAllowlistBackend.isAllowlisted(TEST_PACKAGE_1)).thenReturn(false); + when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, + UID_1, TEST_PACKAGE_1)).thenReturn(AppOpsManager.MODE_IGNORED); + AppStateAppBatteryUsageBridge bridge = + new AppStateAppBatteryUsageBridge(mContext, null, null); + bridge.mAppOpsManager = mAppOpsManager; + bridge.mPowerAllowlistBackend = mPowerAllowlistBackend; + AppEntry entry = new AppEntry(mContext, null, 0); + + bridge.updateExtraInfo(entry, TEST_PACKAGE_1, UID_1); + + assertThat(entry.extraInfo.getClass()) + .isEqualTo(AppStateAppBatteryUsageBridge.AppBatteryUsageDetails.class); + assertThat(AppStateAppBatteryUsageBridge.getAppBatteryUsageDetailsMode(entry)) + .isEqualTo(AppStateAppBatteryUsageBridge.MODE_RESTRICTED); + } + + @Test + public void updateExtraInfo_updatesUnrestricted() { + when(mPowerAllowlistBackend.isAllowlisted(TEST_PACKAGE_1)).thenReturn(true); + when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, + UID_2, TEST_PACKAGE_2)).thenReturn(AppOpsManager.MODE_ALLOWED); + AppStateAppBatteryUsageBridge bridge = + new AppStateAppBatteryUsageBridge(mContext, null, null); + bridge.mAppOpsManager = mAppOpsManager; + bridge.mPowerAllowlistBackend = mPowerAllowlistBackend; + AppEntry entry = new AppEntry(mContext, null, 0); + + bridge.updateExtraInfo(entry, TEST_PACKAGE_2, UID_2); + + assertThat(entry.extraInfo.getClass()) + .isEqualTo(AppStateAppBatteryUsageBridge.AppBatteryUsageDetails.class); + assertThat(AppStateAppBatteryUsageBridge.getAppBatteryUsageDetailsMode(entry)) + .isEqualTo(AppStateAppBatteryUsageBridge.MODE_UNRESTRICTED); + } +}