diff options
40 files changed, 561 insertions, 46 deletions
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml index 379f7bd68..6dfee27d0 100644 --- a/PermissionController/AndroidManifest.xml +++ b/PermissionController/AndroidManifest.xml @@ -66,6 +66,7 @@ <uses-permission android:name="android.permission.START_TASKS_FROM_RECENTS" /> <uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" /> <uses-permission android:name="android.permission.GET_APP_METADATA" /> + <uses-permission android:name="android.permission.NFC_PREFERRED_PAYMENT_INFO" /> <application android:name="com.android.permissioncontroller.PermissionControllerApplication" android:label="@string/app_name" @@ -472,6 +473,18 @@ </intent-filter> </activity> + <activity android:name="com.android.permissioncontroller.role.ui.ChangeDefaultCardEmulationActivity" + android:enabled="@bool/is_at_least_v" + android:excludeFromRecents="true" + android:noHistory="true" + android:exported="true" + android:theme="@android:style/Theme.NoDisplay"> + <intent-filter android:priority="1001"> + <action android:name="android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + <provider android:name="com.android.permissioncontroller.permission.service.PermissionSearchIndexablesProvider" android:authorities="com.android.permissioncontroller" android:multiprocess="false" diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml index 24db04f2d..5e1c7d162 100644 --- a/PermissionController/res/values/strings.xml +++ b/PermissionController/res/values/strings.xml @@ -1223,6 +1223,15 @@ <!-- Search keywords for the NOTES role. [CHAR LIMIT=NONE] --> <string name="role_notes_search_keywords">notes</string> + <!-- Label for the wallet role. [CHAR LIMIT=30] --> + <string name="role_wallet_label">Default wallet app</string> + <!-- Short label for the wallet role. [CHAR LIMIT=30] --> + <string name="role_wallet_short_label">Wallet app</string> + <!-- Description for the wallet role. [CHAR LIMIT=NONE] --> + <string name="role_wallet_description">Wallet apps can store your credit and loyalty cards, car keys and other things to help with various forms of transactions.</string> + <string name="role_wallet_request_title">Set <xliff:g id="app_name" example="Super Wallet">%1$s</xliff:g> as your default wallet app?</string> + <string name="role_wallet_request_description">No permissions needed</string> + <!-- Subtitle for the application that is the current default application [CHAR LIMIT=30] --> <string name="request_role_current_default">Current default</string> diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml index e8f838c81..f09056f73 100644 --- a/PermissionController/res/xml/roles.xml +++ b/PermissionController/res/xml/roles.xml @@ -1655,4 +1655,22 @@ <app-op-permission name="android.permission.PACKAGE_USAGE_STATS" /> </app-op-permissions> </role> + + <role + name="android.app.role.WALLET" + behavior="WalletRoleBehavior" + defaultHolders="config_defaultWallet" + description="@string/role_wallet_description" + exclusive="true" + label="@string/role_wallet_label" + minSdkVersion="35" + overrideUserWhenGranting="true" + requestable="true" + requestDescription="@string/role_wallet_request_description" + requestTitle="@string/role_wallet_request_title" + showNone="true" + shortLabel="@string/role_wallet_short_label" + uiBehavior="WalletRoleUiBehavior"/> + + </roles> diff --git a/PermissionController/role-controller/java/com/android/role/controller/behavior/WalletRoleBehavior.java b/PermissionController/role-controller/java/com/android/role/controller/behavior/WalletRoleBehavior.java new file mode 100644 index 000000000..170c42c3d --- /dev/null +++ b/PermissionController/role-controller/java/com/android/role/controller/behavior/WalletRoleBehavior.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2023 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.role.controller.behavior; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.nfc.NfcAdapter; +import android.nfc.cardemulation.ApduServiceInfo; +import android.nfc.cardemulation.CardEmulation; +import android.nfc.cardemulation.HostApduService; +import android.nfc.cardemulation.OffHostApduService; +import android.os.Build; +import android.os.UserHandle; +import android.permission.flags.Flags; +import android.service.quickaccesswallet.QuickAccessWalletService; +import android.util.ArraySet; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import com.android.modules.utils.build.SdkLevel; +import com.android.role.controller.model.Role; +import com.android.role.controller.model.RoleBehavior; +import com.android.role.controller.util.UserUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Handles the behavior of the wallet role. + */ +@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +public class WalletRoleBehavior implements RoleBehavior { + + private static final String LOG_TAG = WalletRoleBehavior.class.getSimpleName(); + + @Override + public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user, + @NonNull Context context) { + return SdkLevel.isAtLeastV() && Flags.walletRoleEnabled() + && !UserUtils.isProfile(user, context); + } + + @Nullable + @Override + public List<String> getDefaultHoldersAsUser(@NonNull Role role, @NonNull UserHandle user, + @NonNull Context context) { + CardEmulation cardEmulation; + Context userContext = UserUtils.getUserContext(context, user); + try { + cardEmulation = + CardEmulation.getInstance(NfcAdapter.getDefaultAdapter(userContext)); + } catch (UnsupportedOperationException e) { + Log.e(LOG_TAG, "Unsupported Card Emulation Operation.", e); + return null; + } + ApduServiceInfo preferredPaymentService = cardEmulation + .getPreferredPaymentService(); + if (preferredPaymentService != null) { + return Collections.singletonList(preferredPaymentService.getComponent() + .getPackageName()); + } + return null; + } + + @Nullable + @Override + public Boolean isPackageQualifiedAsUser(@NonNull Role role, @NonNull String packageName, + @NonNull UserHandle user, @NonNull Context context) { + return !getQualifyingPackageNamesInternal(packageName, user, context).isEmpty(); + } + + @Nullable + @Override + public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user, + @NonNull Context context) { + return new ArrayList<>(getQualifyingPackageNamesInternal(null, user, context)); + } + + @NonNull + private static Set<String> getQualifyingPackageNamesInternal(@Nullable String packageName, + @NonNull UserHandle user, @NonNull Context context) { + Set<String> packageNames = + resolvePackageNames(HostApduService.SERVICE_INTERFACE, packageName, user, + context); + packageNames.addAll( + resolvePackageNames(OffHostApduService.SERVICE_INTERFACE, packageName, user, + context)); + packageNames.addAll( + resolvePackageNames(QuickAccessWalletService.SERVICE_INTERFACE, packageName, user, + context)); + return packageNames; + } + + @NonNull + private static Set<String> resolvePackageNames(@NonNull String action, + @Nullable String packageName, @NonNull UserHandle user, @NonNull Context context) { + Intent intent = new Intent(action).setPackage(packageName); + PackageManager packageManager = context.getPackageManager(); + List<ResolveInfo> resolveInfos = packageManager + .queryIntentServicesAsUser(intent, 0, user); + Set<String> packageNames = new ArraySet<>(); + int resolveInfosSize = resolveInfos.size(); + for (int i = 0; i < resolveInfosSize; i++) { + packageNames.add(resolveInfos.get(i).serviceInfo.packageName); + } + return packageNames; + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java index c615755be..61ad86a72 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java @@ -56,7 +56,7 @@ import android.text.style.ClickableSpan; import android.util.ArraySet; import android.util.Log; import android.util.Pair; -import android.view.MotionEvent; +import android.view.KeyEvent; import android.view.View; import android.view.View.OnAttachStateChangeListener; import android.view.Window; @@ -686,25 +686,29 @@ public class GrantPermissionsActivity extends SettingsActivity return buttonArray; } - // LINT.IfChange(dispatchTouchEvent) @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - View rootView = getWindow().getDecorView(); - if (rootView.getTop() != 0) { - // We are animating the top view, need to compensate for that in motion events. - ev.setLocation(ev.getX(), ev.getY() - rootView.getTop()); - } - final int x = (int) ev.getX(); - final int y = (int) ev.getY(); - if ((x < 0) || (y < 0) || (x > (rootView.getWidth())) || (y > (rootView.getHeight()))) { - if (MotionEvent.ACTION_DOWN == ev.getAction()) { - mViewHandler.onCancelled(); - } + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_ESCAPE + && event.getRepeatCount() == 0 + && event.hasNoModifiers()) { + event.startTracking(); + mViewHandler.onCancelled(); finishAfterTransition(); + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_ESCAPE + && event.isTracking() + && !event.isCanceled()) { + // Mark it as handled since we did handle the down event + return true; } - return super.dispatchTouchEvent(ev); + return super.onKeyUp(keyCode, event); } - // LINT.ThenChange(PermissionRationaleActivity.java:dispatchTouchEvent) @Override protected void onSaveInstanceState(@NonNull Bundle outState) { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt index cd5f3eb8f..0afbb05fe 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/wear/elements/ListHeader.kt @@ -67,7 +67,6 @@ fun ListHeader( modifier = modifier .defaultMinSize(minHeight = ListHeaderDefaults.Height) - .height(IntrinsicSize.Min) .wrapContentSize() .background(backgroundColor) .padding(contentPadding) diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/ChangeDefaultCardEmulationActivity.java b/PermissionController/src/com/android/permissioncontroller/role/ui/ChangeDefaultCardEmulationActivity.java new file mode 100644 index 000000000..882d01c56 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/ChangeDefaultCardEmulationActivity.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 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.permissioncontroller.role.ui; + +import android.app.Activity; +import android.app.role.RoleManager; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.nfc.cardemulation.CardEmulation; +import android.os.Bundle; +import android.os.Process; +import android.permission.flags.Flags; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.modules.utils.build.SdkLevel; + +import java.util.List; +import java.util.Objects; + +/** + * Activity to handle {@link android.nfc.cardemulation.CardEmulation#ACTION_CHANGE_DEFAULT}. + */ +public class ChangeDefaultCardEmulationActivity extends Activity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent intent; + if (SdkLevel.isAtLeastV() && Flags.walletRoleEnabled()) { + intent = DefaultAppActivity.createIntent(RoleManager.ROLE_WALLET, + Process.myUserHandle(), this); + } else { + intent = getIntent(); + setDefaultPaymentChangeHandlerDialogComponent(intent); + } + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + startActivity(intent); + finish(); + } + + // The only other handler of this intent is in the NFC stack. + private void setDefaultPaymentChangeHandlerDialogComponent(@NonNull Intent intent) { + Intent queryIntent = new Intent(CardEmulation.ACTION_CHANGE_DEFAULT); + PackageManager packageManager = getPackageManager(); + List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(queryIntent, + PackageManager.MATCH_SYSTEM_ONLY); + int resolveInfosSize = resolveInfos.size(); + for (int i = 0; i < resolveInfosSize; i++) { + ResolveInfo resolveInfo = resolveInfos.get(i); + String packageName = resolveInfo.activityInfo.packageName; + if (!Objects.equals(packageName, getPackageName())) { + intent.setClassName(packageName, + resolveInfo.activityInfo.name); + return; + } + } + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java index f9a0193bd..e68fa88a0 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/DefaultAppListChildFragment.java @@ -191,7 +191,8 @@ public class DefaultAppListChildFragment<PF extends PreferenceFragmentCompat preference.setIcon(Utils.getBadgedIcon(context, holderApplicationInfo)); preference.setSummary(Utils.getAppLabel(holderApplicationInfo, context)); } - RoleUiBehaviorUtils.preparePreferenceAsUser(role, rolePreference, user, context); + RoleUiBehaviorUtils.preparePreferenceAsUser(role, holderApplicationInfos, + rolePreference, user, context); preferenceGroup.addPreference(preference); } } diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java index 323325d0b..0142e1c40 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/HomeRoleUiBehavior.java @@ -38,6 +38,8 @@ import com.android.permissioncontroller.role.ui.TwoTargetPreference; import com.android.permissioncontroller.role.utils.UserUtils; import com.android.role.controller.model.Role; +import java.util.List; + /*** * Class for UI behavior of Home role */ @@ -47,7 +49,8 @@ public class HomeRoleUiBehavior implements RoleUiBehavior { @Override public void preparePreferenceAsUser(@NonNull Role role, @NonNull TwoTargetPreference preference, - @NonNull UserHandle user, @NonNull Context context) { + @NonNull List<ApplicationInfo> applicationInfos, @NonNull UserHandle user, + @NonNull Context context) { TwoTargetPreference.OnSecondTargetClickListener listener = null; RoleManager roleManager = context.getSystemService(RoleManager.class); String packageName = CollectionUtils.firstOrNull(roleManager.getRoleHoldersAsUser( diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java index 29dc5d2fc..0a8f9113f 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/RoleUiBehavior.java @@ -28,6 +28,8 @@ import androidx.preference.Preference; import com.android.permissioncontroller.role.ui.TwoTargetPreference; import com.android.role.controller.model.Role; +import java.util.List; + /*** * Interface for UI behavior for roles */ @@ -53,11 +55,13 @@ public interface RoleUiBehavior { * * @param role the role to prepare the preference for * @param preference the {@link Preference} for this role + * @param applicationInfos a list {@link ApplicationInfo} for the current role holders * @param user the user for this role * @param context the {@code Context} to retrieve system services */ default void preparePreferenceAsUser(@NonNull Role role, @NonNull TwoTargetPreference preference, + @NonNull List<ApplicationInfo> applicationInfos, @NonNull UserHandle user, @NonNull Context context) {} diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/WalletRoleUiBehavior.java b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/WalletRoleUiBehavior.java new file mode 100644 index 000000000..858621d74 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/behavior/WalletRoleUiBehavior.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2023 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.permissioncontroller.role.ui.behavior; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.graphics.drawable.Drawable; +import android.nfc.cardemulation.ApduServiceInfo; +import android.nfc.cardemulation.CardEmulation; +import android.nfc.cardemulation.HostApduService; +import android.nfc.cardemulation.OffHostApduService; +import android.os.Build; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.util.Pair; +import androidx.preference.Preference; + +import com.android.permissioncontroller.role.ui.TwoTargetPreference; +import com.android.role.controller.model.Role; +import com.android.role.controller.util.UserUtils; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/*** + * Class for UI behavior of Wallet role + */ +@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) +public class WalletRoleUiBehavior implements RoleUiBehavior { + + private static final String LOG_TAG = WalletRoleUiBehavior.class.getSimpleName(); + + @Override + public void preparePreferenceAsUser(@NonNull Role role, @NonNull TwoTargetPreference preference, + @NonNull List<ApplicationInfo> applicationInfos, @NonNull UserHandle user, + @NonNull Context context) { + Context userContext = UserUtils.getUserContext(context, user); + if (!applicationInfos.isEmpty()) { + preparePreferenceInternal(preference.asPreference(), applicationInfos.get(0), + false, user, userContext); + } + } + + @Override + public void prepareApplicationPreferenceAsUser(@NonNull Role role, + @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo, + @NonNull UserHandle user, @NonNull Context context) { + Context userContext = UserUtils.getUserContext(context, user); + preparePreferenceInternal(preference, applicationInfo, true, user, userContext); + } + + private void preparePreferenceInternal(@NonNull Preference preference, + @NonNull ApplicationInfo applicationInfo, boolean setTitle, @NonNull UserHandle user, + @NonNull Context context) { + if (isSystemApplication(applicationInfo)) { + List<ApduServiceInfo> serviceInfos = getNfcServicesForPackage( + applicationInfo.packageName, user, context); + + Pair<Drawable, CharSequence> bannerAndLabel = + getNonPaymentServiceBannerAndLabelIfExists(serviceInfos, user, context); + if (bannerAndLabel != null) { + preference.setIcon(bannerAndLabel.first); + if (setTitle) { + preference.setTitle(bannerAndLabel.second); + } else { + preference.setSummary(bannerAndLabel.second); + } + } + } + } + + @NonNull + private static List<ApduServiceInfo> getNfcServicesForPackage(@NonNull String packageName, + @NonNull UserHandle user, @NonNull Context context) { + PackageManager packageManager = context.getPackageManager(); + Intent hostApduIntent = new Intent(HostApduService.SERVICE_INTERFACE); + Intent offHostApduIntent = new Intent(OffHostApduService.SERVICE_INTERFACE); + hostApduIntent.setPackage(packageName); + offHostApduIntent.setPackage(packageName); + List<ResolveInfo> hostApduServices = packageManager.queryIntentServicesAsUser( + hostApduIntent, + PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA), user); + List<ResolveInfo> offHostApduServices = packageManager.queryIntentServicesAsUser( + offHostApduIntent, + PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA), user); + List<ApduServiceInfo> nfcServices = new ArrayList<>(); + int apduServiceInfoSize = hostApduServices.size(); + for (int i = 0; i < apduServiceInfoSize; i++) { + ResolveInfo resolveInfo = hostApduServices.get(i); + ApduServiceInfo apduServiceInfo; + try { + apduServiceInfo = new ApduServiceInfo(packageManager, resolveInfo, true); + } catch (XmlPullParserException | IOException e) { + Log.e(LOG_TAG, "Error creating the apduserviceinfo.", e); + continue; + } + nfcServices.add(apduServiceInfo); + } + int offHostApduServiceInfoSize = offHostApduServices.size(); + for (int i = 0; i < offHostApduServiceInfoSize; i++) { + ResolveInfo resolveInfo = offHostApduServices.get(i); + ApduServiceInfo apduServiceInfo; + try { + apduServiceInfo = new ApduServiceInfo(packageManager, resolveInfo, false); + } catch (XmlPullParserException | IOException e) { + Log.e(LOG_TAG, "Error creating the apduserviceinfo.", e); + continue; + } + nfcServices.add(apduServiceInfo); + } + return nfcServices; + } + + @Nullable + private Pair<Drawable, CharSequence> getNonPaymentServiceBannerAndLabelIfExists( + @NonNull List<ApduServiceInfo> apduServiceInfos, @NonNull UserHandle user, + @NonNull Context context) { + Context userContext = UserUtils.getUserContext(context, user); + PackageManager userPackageManager = userContext.getPackageManager(); + Pair<Drawable, CharSequence> bannerAndLabel; + int apduServiceInfoSize = apduServiceInfos.size(); + for (int i = 0; i < apduServiceInfoSize; i++) { + ApduServiceInfo serviceInfo = apduServiceInfos.get(i); + if (serviceInfo.getAids().isEmpty()) { + bannerAndLabel = loadBannerAndLabel(serviceInfo, userPackageManager); + if (bannerAndLabel != null) { + return bannerAndLabel; + } + } else { + List<String> aids = serviceInfo.getAids(); + int aidsSize = aids.size(); + for (int j = 0; j < aidsSize; j++) { + String aid = aids.get(j); + String category = serviceInfo.getCategoryForAid(aid); + if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) { + bannerAndLabel = loadBannerAndLabel(serviceInfo, userPackageManager); + if (bannerAndLabel != null) { + return bannerAndLabel; + } + } + } + } + } + return null; + } + + @Nullable + private Pair<Drawable, CharSequence> loadBannerAndLabel(@NonNull ApduServiceInfo info, + @NonNull PackageManager userPackageManager) { + Drawable drawable = info.loadBanner(userPackageManager); + CharSequence label = info.loadLabel(userPackageManager); + if (drawable != null && !TextUtils.isEmpty(label)) { + return new Pair<>(drawable, label); + } else { + return null; + } + } + + private static boolean isSystemApplication(@NonNull ApplicationInfo applicationInfo) { + return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java index 4b256cef0..b06904930 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/specialappaccess/SpecialAppAccessListChildFragment.java @@ -115,7 +115,8 @@ public class SpecialAppAccessListChildFragment<PF extends PreferenceFragmentComp } else { preference = rolePreference.asPreference(); } - RoleUiBehaviorUtils.preparePreferenceAsUser(role, rolePreference, + RoleUiBehaviorUtils.preparePreferenceAsUser(role, roleItem.getHolderApplicationInfos(), + rolePreference, Process.myUserHandle(), context); preferenceScreen.addPreference(preference); diff --git a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListHelper.kt b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListHelper.kt index 5e8a2f9ad..bbcedea53 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListHelper.kt +++ b/PermissionController/src/com/android/permissioncontroller/role/ui/wear/WearDefaultAppListHelper.kt @@ -61,6 +61,7 @@ class WearDefaultAppListHelper(val context: Context, val user: UserHandle) { .let { RoleUiBehaviorUtils.preparePreferenceAsUser( roleItem.role, + roleItem.holderApplicationInfos, it, user, context diff --git a/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java b/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java index 7ebc1ebd1..5114af536 100644 --- a/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java +++ b/PermissionController/src/com/android/permissioncontroller/role/utils/RoleUiBehaviorUtils.java @@ -33,6 +33,8 @@ import com.android.permissioncontroller.role.ui.UserRestrictionAwarePreference; import com.android.permissioncontroller.role.ui.behavior.RoleUiBehavior; import com.android.role.controller.model.Role; +import java.util.List; + /** * Utility methods for Role UI behavior */ @@ -78,15 +80,15 @@ public final class RoleUiBehaviorUtils { * @see RoleUiBehavior#preparePreferenceAsUser */ public static void preparePreferenceAsUser(@NonNull Role role, - @NonNull RolePreference preference, @NonNull UserHandle user, - @NonNull Context context) { + @NonNull List<ApplicationInfo> applicationInfos, @NonNull RolePreference preference, + @NonNull UserHandle user, @NonNull Context context) { prepareUserRestrictionAwarePreferenceAsUser(role, preference, user, context); RoleUiBehavior uiBehavior = getUiBehavior(role); if (uiBehavior == null) { return; } - uiBehavior.preparePreferenceAsUser(role, preference, user, context); + uiBehavior.preparePreferenceAsUser(role, preference, applicationInfos, user, context); } /** diff --git a/framework-s/api/current.txt b/framework-s/api/current.txt index d54af92f5..d943a03a1 100644 --- a/framework-s/api/current.txt +++ b/framework-s/api/current.txt @@ -14,6 +14,7 @@ package android.app.role { field public static final String ROLE_HOME = "android.app.role.HOME"; field public static final String ROLE_NOTES = "android.app.role.NOTES"; field public static final String ROLE_SMS = "android.app.role.SMS"; + field @FlaggedApi("android.permission.flags.wallet_role_enabled") public static final String ROLE_WALLET = "android.app.role.WALLET"; } } diff --git a/framework-s/java/android/app/role/RoleManager.java b/framework-s/java/android/app/role/RoleManager.java index 3cf1e94ba..fe27d50f3 100644 --- a/framework-s/java/android/app/role/RoleManager.java +++ b/framework-s/java/android/app/role/RoleManager.java @@ -146,6 +146,15 @@ public final class RoleManager { public static final String ROLE_NOTES = "android.app.role.NOTES"; /** + * The name of the Wallet role. + * + * @see android.nfc.cardemulation.CardEmulation + */ + @FlaggedApi(Flags.FLAG_WALLET_ROLE_ENABLED) + @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final String ROLE_WALLET = "android.app.role.WALLET"; + + /** * The name of the system wellbeing role. * * @hide diff --git a/service/java/com/android/safetycenter/SafetyCenterFlags.java b/service/java/com/android/safetycenter/SafetyCenterFlags.java index 67c4d25d6..e51d3a1cf 100644 --- a/service/java/com/android/safetycenter/SafetyCenterFlags.java +++ b/service/java/com/android/safetycenter/SafetyCenterFlags.java @@ -123,6 +123,9 @@ public final class SafetyCenterFlags { private static final String RESURFACE_ISSUE_DELAYS_DEFAULT = ""; private static final Duration RESURFACE_ISSUE_DELAYS_DEFAULT_DURATION = Duration.ofDays(180); + private static final ArraySet<String> sAllowedNotificationSourcesUPlus = + new ArraySet<>(new String[] {"GoogleBackupAndRestore"}); + private static volatile String sUntrackedSourcesDefault = "AndroidAccessibility,AndroidBackgroundLocation," + "AndroidNotificationListener,AndroidPermissionAutoRevoke"; @@ -175,7 +178,10 @@ public final class SafetyCenterFlags { fout.println("FLAGS"); printFlag(fout, PROPERTY_SAFETY_CENTER_ENABLED, getSafetyCenterEnabled()); printFlag(fout, PROPERTY_NOTIFICATIONS_ENABLED, getNotificationsEnabled()); - printFlag(fout, PROPERTY_NOTIFICATIONS_ALLOWED_SOURCES, getNotificationsAllowedSourceIds()); + printFlag( + fout, + PROPERTY_NOTIFICATIONS_ALLOWED_SOURCES, + getNotificationsAllowedSourceIdsFlag()); printFlag(fout, PROPERTY_NOTIFICATIONS_MIN_DELAY, getNotificationsMinDelay()); printFlag( fout, @@ -244,6 +250,20 @@ public final class SafetyCenterFlags { * and therefore this is the only way to enable notifications for sources on Android T. */ public static ArraySet<String> getNotificationsAllowedSourceIds() { + ArraySet<String> sources = getNotificationsAllowedSourceIdsFlag(); + if (SdkLevel.isAtLeastU()) { + // This is a hack to update the flag value via mainline update. Reasons why we can't do + // this via: + // remote flag update - these are generally avoided and considered risky + // XML config - it would break GTS tests for OEMs that have a separate config copy + // default flag value - it would also require a remote flag update + sources.addAll(sAllowedNotificationSourcesUPlus); + } + + return sources; + } + + private static ArraySet<String> getNotificationsAllowedSourceIdsFlag() { return getCommaSeparatedStrings(PROPERTY_NOTIFICATIONS_ALLOWED_SOURCES); } diff --git a/tests/cts/permission/Android.bp b/tests/cts/permission/Android.bp index c7c5f9279..ed7fcea25 100644 --- a/tests/cts/permission/Android.bp +++ b/tests/cts/permission/Android.bp @@ -54,6 +54,7 @@ android_test { "platform-test-rules", "CtsVirtualDeviceCommonLib", "android.permission.flags-aconfig-java", + "androidx.test.rules", ], jni_libs: [ "libctspermission_jni", diff --git a/tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java b/tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java index 294896d97..fa43bfb18 100644 --- a/tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/AppWidgetManagerPermissionTest.java @@ -20,7 +20,8 @@ import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.pm.PackageManager; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Test that protected AppWidgetManager APIs cannot be called without permissions diff --git a/tests/cts/permission/src/android/permission/cts/CameraPermissionTest.java b/tests/cts/permission/src/android/permission/cts/CameraPermissionTest.java index 99f2862f1..981735388 100644 --- a/tests/cts/permission/src/android/permission/cts/CameraPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/CameraPermissionTest.java @@ -19,7 +19,8 @@ package android.permission.cts; import android.hardware.Camera; import android.os.Environment; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; + +import androidx.test.filters.MediumTest; import java.io.FileOutputStream; diff --git a/tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java b/tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java index 984fd6cfe..69b64d790 100644 --- a/tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java +++ b/tests/cts/permission/src/android/permission/cts/ContactsProviderTest.java @@ -19,7 +19,8 @@ package android.permission.cts; import android.content.ContentValues; import android.provider.ContactsContract; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Verify permissions are enforced. diff --git a/tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java index 835ba124c..3d9ba8214 100644 --- a/tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoActivityRelatedPermissionTest.java @@ -21,7 +21,8 @@ import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.test.ActivityInstrumentationTestCase2; -import android.test.suitebuilder.annotation.MediumTest; + +import androidx.test.filters.MediumTest; import java.util.List; diff --git a/tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java index c2c42a10d..50498b1d5 100644 --- a/tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoAudioPermissionTest.java @@ -25,9 +25,10 @@ import android.media.AudioManager; import android.media.AudioRecord; import android.media.MediaRecorder; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; +import androidx.test.filters.SmallTest; + /** * Verify the audio related operations require specific permissions. */ diff --git a/tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java index 5630c5b8c..1a46842b2 100644 --- a/tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoBroadcastPackageRemovedPermissionTest.java @@ -19,7 +19,8 @@ package android.permission.cts; import android.content.Intent; import android.os.Bundle; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Verify Context related methods without specific BROADCAST series permissions. diff --git a/tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java index 6ad048308..e0573044c 100644 --- a/tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoCaptureVideoPermissionTest.java @@ -22,9 +22,10 @@ import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.ImageReader; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; import android.util.DisplayMetrics; +import androidx.test.filters.SmallTest; + /** * Verify the capture system video output permission requirements. */ diff --git a/tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java index 5a0b25993..ac77947d9 100644 --- a/tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoKeyPermissionTest.java @@ -19,7 +19,8 @@ package android.permission.cts; import android.app.KeyguardManager; import android.content.Context; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Verify the key input related operations require specific permissions. diff --git a/tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.java index b8d2ee21a..5dc73d520 100644 --- a/tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoNetworkStatePermissionTest.java @@ -19,7 +19,8 @@ package android.permission.cts; import android.content.Context; import android.net.ConnectivityManager; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; import java.net.InetAddress; diff --git a/tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java index b6fb84dc7..f0d70b2ce 100644 --- a/tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoReadLogsPermissionTest.java @@ -21,7 +21,8 @@ import android.system.Os; import android.system.OsConstants; import android.system.StructStat; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; + +import androidx.test.filters.MediumTest; import java.io.BufferedReader; import java.io.IOException; diff --git a/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java index 51b3bd830..437aa19c4 100644 --- a/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoSystemFunctionPermissionTest.java @@ -28,7 +28,8 @@ import android.os.VibratorManager; import android.platform.test.annotations.AppModeFull; import android.telephony.gsm.SmsManager; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; import java.io.IOException; import java.io.InputStream; diff --git a/tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java b/tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java index 030f341aa..95c4da727 100644 --- a/tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoWakeLockPermissionTest.java @@ -26,7 +26,8 @@ import android.net.wifi.WifiManager.WifiLock; import android.os.PowerManager; import android.platform.test.annotations.AppModeFull; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Verify the Wake Lock related operations require specific permissions. diff --git a/tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java b/tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java index 18e4375bc..fc1d6b59f 100644 --- a/tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java +++ b/tests/cts/permission/src/android/permission/cts/NoWallpaperPermissionsTest.java @@ -27,7 +27,8 @@ import android.content.Context; import android.graphics.Bitmap; import android.platform.test.annotations.AppModeFull; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; import org.junit.function.ThrowingRunnable; diff --git a/tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java b/tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java index df3ec3c64..7ebb09f98 100644 --- a/tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java +++ b/tests/cts/permission/src/android/permission/cts/PackageManagerRequiringPermissionsTest.java @@ -21,7 +21,8 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.platform.test.annotations.AppModeFull; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Verify the PackageManager related operations require specific permissions. diff --git a/tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java b/tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java index 9f5a813d1..83c2ffaee 100644 --- a/tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/ProviderPermissionTest.java @@ -35,12 +35,11 @@ import android.provider.ContactsContract; import android.provider.Settings; import android.provider.Telephony; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; import android.util.Log; import androidx.test.InstrumentationRegistry; +import androidx.test.filters.MediumTest; -import java.util.ArrayList; import java.util.List; import java.util.Objects; diff --git a/tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java b/tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java index b1d3d5afb..13f17dce8 100644 --- a/tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java +++ b/tests/cts/permission/src/android/permission/cts/RebootPermissionTest.java @@ -18,7 +18,8 @@ package android.permission.cts; import android.content.Intent; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Verify that rebooting requires Permission. diff --git a/tests/cts/permissionpolicy/Android.bp b/tests/cts/permissionpolicy/Android.bp index a6ef8bde2..7a481b7ff 100644 --- a/tests/cts/permissionpolicy/Android.bp +++ b/tests/cts/permissionpolicy/Android.bp @@ -37,6 +37,7 @@ android_test { "androidx.test.ext.junit-nodeps", "truth", "permission-test-util-lib", + "androidx.test.rules", ], srcs: [ "src/**/*.java", diff --git a/tests/cts/permissionpolicy/res/raw/android_manifest.xml b/tests/cts/permissionpolicy/res/raw/android_manifest.xml index 3f53bf863..a721a9f6f 100644 --- a/tests/cts/permissionpolicy/res/raw/android_manifest.xml +++ b/tests/cts/permissionpolicy/res/raw/android_manifest.xml @@ -6974,6 +6974,16 @@ android:label="@string/permlab_foregroundServiceFileManagement" android:protectionLevel="normal|instant" /> + <!-- @FlaggedApi("android.content.pm.introduce_media_processing_type") + Allows a regular application to use {@link android.app.Service#startForeground + Service.startForeground} with the type "mediaProcessing". + <p>Protection level: normal|instant + --> + <permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROCESSING" + android:description="@string/permdesc_foregroundServiceMediaProcessing" + android:label="@string/permlab_foregroundServiceMediaProcessing" + android:protectionLevel="normal|instant" /> + <!-- Allows a regular application to use {@link android.app.Service#startForeground Service.startForeground} with the type "specialUse". <p>Protection level: normal|appop|instant diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java index d8dc11a14..f34170a9b 100644 --- a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java +++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/ContactsProviderTest.java @@ -21,7 +21,8 @@ import android.content.ContentValues; import android.platform.test.annotations.AppModeFull; import android.provider.ContactsContract; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Verify that deprecated contacts permissions are not enforced. diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java index 0f7638694..ef38573ab 100644 --- a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java +++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/NoCaptureAudioOutputPermissionTest.java @@ -21,7 +21,8 @@ import android.media.AudioFormat; import android.media.AudioRecord; import android.media.MediaRecorder.AudioSource; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Verify the capture system video output permission requirements. diff --git a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java index b02b32f22..8bf3e83a4 100644 --- a/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java +++ b/tests/cts/permissionpolicy/src/android/permissionpolicy/cts/PermissionMaxSdkVersionTest.java @@ -19,7 +19,8 @@ package android.permission.cts; import android.content.pm.PackageManager; import android.os.Process; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; + +import androidx.test.filters.SmallTest; /** * Verify permission behaviors with android:maxSdkVersion diff --git a/tests/hostside/safetycenter/src/android/safetycenter/hostside/rules/RequireSafetyCenterRule.kt b/tests/hostside/safetycenter/src/android/safetycenter/hostside/rules/RequireSafetyCenterRule.kt index edf76e888..fe75a05a2 100644 --- a/tests/hostside/safetycenter/src/android/safetycenter/hostside/rules/RequireSafetyCenterRule.kt +++ b/tests/hostside/safetycenter/src/android/safetycenter/hostside/rules/RequireSafetyCenterRule.kt @@ -24,14 +24,23 @@ import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement +/** toBooleanString() doesn't seem available on all Kotlin versions we need to support. */ +private fun String.toBooleanStrictInt(): Boolean = + when (this) { + "true" -> true + "false" -> false + else -> + throw IllegalArgumentException("The string doesn't represent a boolean value: $this") + } + /** JUnit rule for host side tests that requires Safety Center to be supported and enabled. */ class RequireSafetyCenterRule(private val hostTestClass: BaseHostJUnit4Test) : TestRule { private val safetyCenterSupported: Boolean by lazy { - shellCommandStdoutOrThrow("cmd safety_center supported").toBooleanStrict() + shellCommandStdoutOrThrow("cmd safety_center supported").toBooleanStrictInt() } private val safetyCenterEnabled: Boolean by lazy { - shellCommandStdoutOrThrow("cmd safety_center enabled").toBooleanStrict() + shellCommandStdoutOrThrow("cmd safety_center enabled").toBooleanStrictInt() } override fun apply(base: Statement, description: Description): Statement { |