diff options
47 files changed, 1085 insertions, 603 deletions
diff --git a/PermissionController/AndroidManifest.xml b/PermissionController/AndroidManifest.xml index b85e5ebdc..325597e67 100644 --- a/PermissionController/AndroidManifest.xml +++ b/PermissionController/AndroidManifest.xml @@ -356,11 +356,6 @@ </intent-filter> </activity> - <activity android:name="com.android.permissioncontroller.permission.ui.OverlayWarningDialog" - android:excludeFromRecents="true" - android:exported="false" - android:theme="@style/Theme.DeviceDefault.Dialog.NoActionBar.DayNight" /> - <activity android:name="com.android.permissioncontroller.permission.ui.LocationProviderInterceptDialog" android:excludeFromRecents="true" android:exported="false" @@ -599,6 +594,8 @@ </intent-filter> </activity> + <!-- Unexported empty activity for in-process tests --> + <activity android:name="android.app.Activity" /> </application> </manifest> diff --git a/PermissionController/res/values-v34/styles.xml b/PermissionController/res/values-v34/styles.xml index f427d1a8e..cc119a331 100644 --- a/PermissionController/res/values-v34/styles.xml +++ b/PermissionController/res/values-v34/styles.xml @@ -149,6 +149,7 @@ <item name="android:gravity">center</item> <item name="android:insetTop">6dp</item> <item name="android:insetBottom">6dp</item> + <item name="android:minWidth">48dp</item> <item name="android:minHeight">48dp</item> <item name="android:paddingTop">8dp</item> <item name="android:paddingBottom">8dp</item> diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml index 8c9f86d70..8399147c3 100644 --- a/PermissionController/res/values/strings.xml +++ b/PermissionController/res/values/strings.xml @@ -330,19 +330,6 @@ <!-- Title of the permission dialog for accessibility purposes- spoken to the user. [CHAR LIMIT=none] --> <string name="permission_request_title">Permission request</string> - <!-- Title for the dialog that warns the user they need to turn off screen overlays - before permissions can be changed. [CHAR LIMIT=NONE] --> - <string name="screen_overlay_title">Screen overlay detected</string> - - <!-- Message for the dialog that warns the user they need to turn off screen overlays - before permissions can be changed. The "Settings > Apps" conveys to the user to - go to Settings and click on apps, this may need updates in RTL languages. [CHAR LIMIT=NONE] --> - <string name="screen_overlay_message">To change this permission setting, you first have to turn off the screen overlay from Settings \u003e Apps</string> - - <!-- Button for the dialog that warns the user they need to turn off screen overlays - before permissions can be changed. [CHAR LIMIT=NONE] --> - <string name="screen_overlay_button">Open settings</string> - <!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=30] --> <string name="wear_not_allowed_dlg_title">Android Wear</string> <!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=none] --> diff --git a/PermissionController/res/xml/roles.xml b/PermissionController/res/xml/roles.xml index d225d296a..dc884e810 100644 --- a/PermissionController/res/xml/roles.xml +++ b/PermissionController/res/xml/roles.xml @@ -68,12 +68,8 @@ </permission-set> <permission-set name="sensors"> - <permission name="android.permission.BODY_SENSORS"/> - <permission name="android.permission.BODY_SENSORS_BACKGROUND" minSdkVersion="33"/> - <permission name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" - minSdkVersion="34"/> - <permission name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND" - minSdkVersion="34"/> + <permission name="android.permission.BODY_SENSORS" /> + <permission name="android.permission.BODY_SENSORS_BACKGROUND" minSdkVersion="33" /> </permission-set> <permission-set name="storage"> diff --git a/PermissionController/src/com/android/permissioncontroller/Constants.java b/PermissionController/src/com/android/permissioncontroller/Constants.java index e16f8d758..3995b608e 100644 --- a/PermissionController/src/com/android/permissioncontroller/Constants.java +++ b/PermissionController/src/com/android/permissioncontroller/Constants.java @@ -312,4 +312,19 @@ public class Constants { @RequiresApi(Build.VERSION_CODES.TIRAMISU) public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio"; + + /** + * Extra used by Settings to indicate an Intent should be treated as if opened directly by + * Settings app itself. + */ + public static final String EXTRA_FROM_SETTINGS = "is_from_settings_homepage"; + + /** + * Extra used by Settings to indicate an Intent should be treated as if opened by a slice + * within Settings. + * + * <p>Slices are opened within settings by firing a PendingIntent, so we can use this extra to + * allow the same UX path to be taken as for slices. + */ + public static final String EXTRA_IS_FROM_SLICE = "is_from_slice"; } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java b/PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java new file mode 100644 index 000000000..637eb5fc4 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompat.java @@ -0,0 +1,84 @@ +/* + * 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.permission.compat; + +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; +import android.text.method.LinkMovementMethod; +import android.text.method.Touch; +import android.view.MotionEvent; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +/** + * Fixes the issue that links can be triggered for touches outside of line bounds for + * {@link LinkMovementMethod}. + * <p> + * This is based on the fix in ag/22301465. + */ +public class LinkMovementMethodCompat extends LinkMovementMethod { + @Override + public boolean onTouchEvent(@NonNull TextView widget, @NonNull Spannable buffer, + @NonNull MotionEvent event) { + int action = event.getAction(); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + boolean isOutOfLineBounds; + if (y < 0 || y > layout.getHeight()) { + isOutOfLineBounds = true; + } else { + int line = layout.getLineForVertical(y); + isOutOfLineBounds = x < layout.getLineLeft(line) || x > layout.getLineRight(line); + } + + if (isOutOfLineBounds) { + Selection.removeSelection(buffer); + + // return LinkMovementMethod.super.onTouchEvent(widget, buffer, event); + return Touch.onTouchEvent(widget, buffer, event); + } + } + + return super.onTouchEvent(widget, buffer, event); + } + + /** + * @return a {@link LinkMovementMethodCompat} instance + */ + @NonNull + public static LinkMovementMethodCompat getInstance() { + if (sInstance == null) { + sInstance = new LinkMovementMethodCompat(); + } + + return sInstance; + } + + private static LinkMovementMethodCompat sInstance; +} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java b/PermissionController/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java index 53ba48ace..196bfc6af 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java @@ -38,8 +38,8 @@ import androidx.annotation.Nullable; import com.android.modules.utils.build.SdkLevel; import com.android.permissioncontroller.R; import com.android.permissioncontroller.permission.model.AppPermissionGroup; -import com.android.permissioncontroller.permission.utils.SubattributionUtils; import com.android.permissioncontroller.permission.utils.Utils; +import com.android.permissioncontroller.permission.utils.v31.SubattributionUtils; import java.util.ArrayList; import java.util.Collections; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java index 3c1a9df39..001520c1b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/service/PermissionControllerServiceImpl.java @@ -34,6 +34,7 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; +import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Process; @@ -50,6 +51,7 @@ import android.util.Xml; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.android.permissioncontroller.PermissionControllerProto.PermissionControllerDumpProto; import com.android.permissioncontroller.PermissionControllerStatsLog; @@ -59,12 +61,12 @@ import com.android.permissioncontroller.permission.model.Permission; import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo; import com.android.permissioncontroller.permission.model.livedatatypes.AppPermGroupUiInfo.PermGrantState; import com.android.permissioncontroller.permission.ui.AutoGrantPermissionsNotifier; -import com.android.permissioncontroller.permission.utils.AdminRestrictedPermissionsUtils; import com.android.permissioncontroller.permission.utils.ArrayUtils; import com.android.permissioncontroller.permission.utils.KotlinUtils; import com.android.permissioncontroller.permission.utils.PermissionMapping; import com.android.permissioncontroller.permission.utils.UserSensitiveFlagsUtils; import com.android.permissioncontroller.permission.utils.Utils; +import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils; import com.android.role.controller.model.Role; import com.android.role.controller.model.Roles; @@ -777,6 +779,7 @@ public final class PermissionControllerServiceImpl extends PermissionControllerL } @Override + @RequiresApi(Build.VERSION_CODES.TIRAMISU) public void onRevokeSelfPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions, @NonNull Runnable callback) { PackageInfo pkgInfo = getPkgInfo(packageName); diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java index 7c4f197b2..a475a89ac 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/GrantPermissionsActivity.java @@ -741,7 +741,7 @@ public class GrantPermissionsActivity extends SettingsActivity removeActivityFromMap(); // If a new merge request came in before we managed to remove this activity from the // map, then cancel the result set for now. - if (!oldRequestedPermissions.equals(mRequestedPermissions)) { + if (!Objects.equals(oldRequestedPermissions, mRequestedPermissions)) { return false; } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/OverlayWarningDialog.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/OverlayWarningDialog.java deleted file mode 100644 index de1f68c98..000000000 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/OverlayWarningDialog.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2015 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.permission.ui; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.ActivityNotFoundException; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.DialogInterface.OnDismissListener; -import android.content.Intent; -import android.os.Bundle; -import android.provider.Settings; -import android.util.Log; - -import com.android.permissioncontroller.R; - -public class OverlayWarningDialog extends Activity implements OnClickListener, OnDismissListener { - - private static final String TAG = "OverlayWarningDialog"; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - new AlertDialog.Builder(this) - .setTitle(R.string.screen_overlay_title) - .setMessage(R.string.screen_overlay_message) - .setPositiveButton(R.string.screen_overlay_button, this) - .setOnDismissListener(this) - .show(); - } - - @Override - public void onDismiss(DialogInterface dialog) { - finish(); - } - - @Override - public void onClick(DialogInterface dialog, int which) { - finish(); - try { - startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)); - } catch (ActivityNotFoundException e) { - Log.w(TAG, "No manage overlay settings", e); - } - } - -} diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java index ad6b993fa..2de936469 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/AutoAppPermissionFragment.java @@ -55,11 +55,11 @@ import androidx.preference.TwoStatePreference; import com.android.car.ui.AlertDialogBuilder; import com.android.permissioncontroller.R; import com.android.permissioncontroller.auto.AutoSettingsFrameFragment; -import com.android.permissioncontroller.permission.ui.AdvancedConfirmDialogArgs; import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory; +import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs; import com.android.permissioncontroller.permission.utils.KotlinUtils; import com.android.permissioncontroller.permission.utils.PackageRemovalMonitor; import com.android.settingslib.RestrictedLockUtils; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt index a7cb6c340..7ea400127 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionHistoryPreference.kt @@ -24,7 +24,7 @@ import androidx.preference.Preference.OnPreferenceClickListener import com.android.car.ui.preference.CarUiPreference import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModel -import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelLegacy +import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelLegacy /** Preference that displays a permission usage for an app. */ @RequiresApi(Build.VERSION_CODES.S) diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt index eee9e8b6e..a16bc1ce4 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageDetailsFragment.kt @@ -42,8 +42,8 @@ import com.android.permissioncontroller.permission.model.v31.PermissionUsages import com.android.permissioncontroller.permission.model.v31.PermissionUsages.PermissionsUsagesChangeCallback import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity import com.android.permissioncontroller.permission.ui.auto.AutoDividerPreference -import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelFactoryLegacy -import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageDetailsViewModelLegacy +import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelFactoryLegacy +import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageDetailsViewModelLegacy import com.android.permissioncontroller.permission.utils.KotlinUtils.getPermGroupLabel import com.android.permissioncontroller.permission.utils.Utils import java.time.Clock diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt index a21e257c3..d4a2a073e 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/auto/dashboard/AutoPermissionUsageFragment.kt @@ -40,9 +40,9 @@ import com.android.permissioncontroller.permission.model.v31.PermissionUsages import com.android.permissioncontroller.permission.model.v31.PermissionUsages.PermissionsUsagesChangeCallback import com.android.permissioncontroller.permission.ui.model.ManagePermissionsViewModel import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageControlPreferenceUtils -import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageViewModelFactoryLegacy -import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageViewModelLegacy -import com.android.permissioncontroller.permission.ui.model.v31.PermissionUsageViewModelLegacy.PermissionGroupWithUsageCount +import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageViewModelFactoryLegacy +import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageViewModelLegacy +import com.android.permissioncontroller.permission.ui.legacy.PermissionUsageViewModelLegacy.PermissionGroupWithUsageCount import com.android.permissioncontroller.permission.utils.Utils @RequiresApi(Build.VERSION_CODES.S) diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java index bacf50144..946484626 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java @@ -74,13 +74,13 @@ import androidx.lifecycle.ViewModelProvider; import com.android.modules.utils.build.SdkLevel; import com.android.permissioncontroller.R; import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState; -import com.android.permissioncontroller.permission.ui.AdvancedConfirmDialogArgs; import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory; +import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs; import com.android.permissioncontroller.permission.utils.KotlinUtils; import com.android.permissioncontroller.permission.utils.Utils; import com.android.settingslib.RestrictedLockUtils; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java index 90d7204cf..220507426 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/PermissionAppsFragment.java @@ -53,6 +53,7 @@ import com.android.permissioncontroller.permission.model.v31.AppPermissionUsage; import com.android.permissioncontroller.permission.model.v31.PermissionUsages; import com.android.permissioncontroller.permission.ui.Category; import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity; +import com.android.permissioncontroller.permission.ui.handheld.v31.CardViewPreference; import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModel; import com.android.permissioncontroller.permission.ui.model.PermissionAppsViewModelFactory; import com.android.permissioncontroller.permission.utils.KotlinUtils; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/CardViewPreference.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/CardViewPreference.java index 6c76d906b..008813a83 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/CardViewPreference.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v31/CardViewPreference.java @@ -14,13 +14,15 @@ * limitations under the License. */ -package com.android.permissioncontroller.permission.ui.handheld; +package com.android.permissioncontroller.permission.ui.handheld.v31; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.view.View; import android.widget.Button; +import androidx.annotation.RequiresApi; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; @@ -30,6 +32,7 @@ import com.android.permissioncontroller.R; /** * A Preference representing a banner message represented as a CardView */ +@RequiresApi(Build.VERSION_CODES.S) public class CardViewPreference extends Preference { private String mAction; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt index f3abc7619..88b5ebe87 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/AppDataSharingUpdatesFooterPreference.kt @@ -18,7 +18,6 @@ package com.android.permissioncontroller.permission.ui.handheld.v34 import android.content.Context import android.text.SpannableString -import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan import android.util.AttributeSet import android.view.View @@ -26,6 +25,7 @@ import android.widget.TextView import androidx.preference.Preference import androidx.preference.PreferenceViewHolder import com.android.permissioncontroller.R +import com.android.permissioncontroller.permission.compat.LinkMovementMethodCompat /** A preference for a footer with an icon and a link. */ class AppDataSharingUpdatesFooterPreference : Preference { @@ -80,7 +80,7 @@ class AppDataSharingUpdatesFooterPreference : Preference { footerLinkView?.let { it.visibility = if (onFooterLinkClick == null) View.GONE else View.VISIBLE it.text = footerLinkText - it.movementMethod = LinkMovementMethod.getInstance() + it.movementMethod = LinkMovementMethodCompat.getInstance() } super.onBindViewHolder(holder) } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt index 7504ae7d4..3998ca141 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/v34/PermissionRationaleViewHandlerImpl.kt @@ -21,7 +21,6 @@ import android.content.Context import android.content.res.ColorStateList import android.os.Build import android.os.Bundle -import android.text.method.LinkMovementMethod import android.transition.ChangeBounds import android.transition.TransitionManager import android.view.Gravity @@ -29,13 +28,13 @@ import android.view.LayoutInflater import android.view.View import android.view.View.OnClickListener import android.view.ViewGroup -import android.view.WindowManager import android.view.animation.AnimationUtils import android.widget.Button import android.widget.LinearLayout import android.widget.TextView import androidx.annotation.RequiresApi import com.android.permissioncontroller.R +import com.android.permissioncontroller.permission.compat.LinkMovementMethodCompat import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleViewHandler.Result.Companion.CANCELLED @@ -146,16 +145,16 @@ class PermissionRationaleViewHandlerImpl( titleView = rootView.findViewById(R.id.permission_rationale_title) dataSharingSourceMessageView = rootView.findViewById(R.id.data_sharing_source_message) - dataSharingSourceMessageView!!.movementMethod = LinkMovementMethod.getInstance() + dataSharingSourceMessageView!!.movementMethod = LinkMovementMethodCompat.getInstance() purposeTitleView = rootView.findViewById(R.id.purpose_title) purposeMessageView = rootView.findViewById(R.id.purpose_message) learnMoreMessageView = rootView.findViewById(R.id.learn_more_message) - learnMoreMessageView!!.movementMethod = LinkMovementMethod.getInstance() + learnMoreMessageView!!.movementMethod = LinkMovementMethodCompat.getInstance() settingsMessageView = rootView.findViewById(R.id.settings_message) - settingsMessageView!!.movementMethod = LinkMovementMethod.getInstance() + settingsMessageView!!.movementMethod = LinkMovementMethodCompat.getInstance() if (!shouldShowSettingsSection) { val settingsSectionView: ViewGroup? = rootView.findViewById(R.id.settings_section) diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelLegacy.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt index 667667200..e219153f3 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModelLegacy.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageDetailsViewModelLegacy.kt @@ -15,7 +15,7 @@ */ @file:Suppress("DEPRECATION") -package com.android.permissioncontroller.permission.ui.model.v31 +package com.android.permissioncontroller.permission.ui.legacy import android.Manifest import android.app.AppOpsManager @@ -44,7 +44,7 @@ import com.android.permissioncontroller.permission.utils.KotlinUtils import com.android.permissioncontroller.permission.utils.KotlinUtils.getPackageLabel import com.android.permissioncontroller.permission.utils.PermissionMapping import com.android.permissioncontroller.permission.utils.StringUtils -import com.android.permissioncontroller.permission.utils.SubattributionUtils +import com.android.permissioncontroller.permission.utils.v31.SubattributionUtils import com.android.permissioncontroller.permission.utils.Utils import java.time.Instant import java.util.concurrent.TimeUnit diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModelLegacy.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageViewModelLegacy.kt index ff21a7216..d0e751f7d 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageViewModelLegacy.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/legacy/PermissionUsageViewModelLegacy.kt @@ -15,7 +15,7 @@ */ @file:Suppress("DEPRECATION") -package com.android.permissioncontroller.permission.ui.model.v31 +package com.android.permissioncontroller.permission.ui.legacy import android.Manifest import android.app.LoaderManager diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt index ddae7c0ee..cc44a400b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt @@ -63,7 +63,7 @@ import com.android.permissioncontroller.permission.model.livedatatypes.LightAppP import com.android.permissioncontroller.permission.model.livedatatypes.LightPermission import com.android.permissioncontroller.permission.service.PermissionChangeStorageImpl import com.android.permissioncontroller.permission.service.v33.PermissionDecisionStorageImpl -import com.android.permissioncontroller.permission.ui.AdvancedConfirmDialogArgs +import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_ALWAYS import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType.ALLOW_FOREGROUND @@ -85,6 +85,7 @@ import com.android.permissioncontroller.permission.utils.PermissionMapping.getPa import com.android.permissioncontroller.permission.utils.SafetyNetLogger import com.android.permissioncontroller.permission.utils.Utils import com.android.permissioncontroller.permission.utils.navigateSafe +import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils import com.android.settingslib.RestrictedLockUtils import java.util.Random import kotlin.collections.component1 @@ -206,7 +207,7 @@ class AppPermissionViewModel( return } - value = PermissionMapping.getSafetyLabelSharingPurposesForGroup( + value = SafetyLabelUtils.getSafetyLabelSharingPurposesForGroup( safetyLabel, permGroupName).any() } } diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt index 7cd1ea325..c6d89876b 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/GrantPermissionsViewModel.kt @@ -118,7 +118,7 @@ import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_INTERACTED import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity.EXTRA_RESULT_PERMISSION_RESULT import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity -import com.android.permissioncontroller.permission.utils.AdminRestrictedPermissionsUtils +import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils import com.android.permissioncontroller.permission.utils.KotlinUtils import com.android.permissioncontroller.permission.utils.KotlinUtils.getDefaultPrecision import com.android.permissioncontroller.permission.utils.KotlinUtils.grantBackgroundRuntimePermissions @@ -130,6 +130,7 @@ import com.android.permissioncontroller.permission.utils.PermissionMapping import com.android.permissioncontroller.permission.utils.PermissionMapping.getPartialStorageGrantPermissionsForGroup import com.android.permissioncontroller.permission.utils.SafetyNetLogger import com.android.permissioncontroller.permission.utils.Utils +import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils /** * ViewModel for the GrantPermissionsActivity. Tracks all permission groups that are affected by @@ -302,8 +303,9 @@ class GrantPermissionsViewModel( appPermGroup.permissions[perm]?.isGrantedIncludingAppOp == true && appPermGroup.permissions[perm]?.isRevokeWhenRequested == false } - if (allAffectedGranted) { - groupStates[key]!!.state = STATE_ALLOWED + if (allAffectedGranted || isCompatStorageGrant(appPermGroup)) { + groupStates[key] = GroupState(appPermGroup, state.isBackground, + state.affectedPermissions, STATE_ALLOWED) } } } else { @@ -625,7 +627,7 @@ class GrantPermissionsViewModel( return false } - val purposes = PermissionMapping.getSafetyLabelSharingPurposesForGroup(safetyLabel, + val purposes = SafetyLabelUtils.getSafetyLabelSharingPurposesForGroup(safetyLabel, permissionGroupName) return purposes.isNotEmpty() } @@ -804,6 +806,11 @@ class GrantPermissionsViewModel( // then skip the request return STATE_SKIPPED } + // If the "false grant" for apps that don't support the permission has been applied, + // treat the permission as already granted + if (isCompatStorageGrant(group)) { + return STATE_ALLOWED + } } val isBackground = perm in group.backgroundPermNames @@ -876,7 +883,7 @@ class GrantPermissionsViewModel( * ACCESS_MEDIA_LOCATION granted */ private fun isPartialStorageGrant(group: LightAppPermGroup): Boolean { - if (!KotlinUtils.isPhotoPickerPromptEnabled() || group.permGroupName != READ_MEDIA_VISUAL) { + if (group.permGroupName != READ_MEDIA_VISUAL || !KotlinUtils.isPhotoPickerPromptEnabled()) { return false } @@ -886,6 +893,20 @@ class GrantPermissionsViewModel( } } + /** + * A compat storage grant is provided when the user selects "select photos" on an app that does + * not explicitly request the READ_MEDIA_VISUAL_USER_SELECTED permission. It grants RMVUS, and + * applies the "revoked compat" state to all other permissions in the group. + */ + private fun isCompatStorageGrant(group: LightAppPermGroup): Boolean { + if (group.permGroupName != READ_MEDIA_VISUAL || !KotlinUtils.isPhotoPickerPromptEnabled()) { + return false + } + return group.permissions[READ_MEDIA_VISUAL_USER_SELECTED] + ?.isGrantedIncludingAppOp == true && + group.permissions.values.any { it.isCompatRevoked } + } + private fun getStateFromPolicy(perm: String, group: LightAppPermGroup): Int { val isBackground = perm in group.backgroundPermNames var skipGroup = false diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt index 00df47d3e..7633859fb 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v31/PermissionUsageDetailsViewModel.kt @@ -55,7 +55,7 @@ import com.android.permissioncontroller.permission.ui.handheld.v31.shouldShowSub import com.android.permissioncontroller.permission.utils.KotlinUtils import com.android.permissioncontroller.permission.utils.KotlinUtils.getPackageLabel import com.android.permissioncontroller.permission.utils.PermissionMapping -import com.android.permissioncontroller.permission.utils.SubattributionUtils +import com.android.permissioncontroller.permission.utils.v31.SubattributionUtils import com.android.permissioncontroller.permission.utils.Utils import java.time.Instant import java.util.Objects diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt index caf073632..561a8eef2 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/v34/PermissionRationaleViewModel.kt @@ -46,7 +46,7 @@ import com.android.permissioncontroller.permission.ui.ManagePermissionsActivity. import com.android.permissioncontroller.permission.ui.v34.PermissionRationaleActivity import com.android.permissioncontroller.permission.utils.KotlinUtils import com.android.permissioncontroller.permission.utils.KotlinUtils.getAppStoreIntent -import com.android.permissioncontroller.permission.utils.PermissionMapping +import com.android.permissioncontroller.permission.utils.v34.SafetyLabelUtils import com.android.settingslib.HelpUtils /** @@ -124,9 +124,9 @@ class PermissionRationaleViewModel( KotlinUtils.getPackageLabel(app, it, Process.myUserHandle()) } - val purposes = PermissionMapping.getSafetyLabelSharingPurposesForGroup( + val purposes = SafetyLabelUtils.getSafetyLabelSharingPurposesForGroup( safetyLabelInfo.safetyLabel, permissionGroupName) - if (isStale) { + if (value == null) { logPermissionRationaleDialogViewed(purposes) } value = diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java index a5961dcec..e2df47009 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/AppPermissionFragment.java @@ -66,13 +66,13 @@ import com.android.permissioncontroller.R; import com.android.permissioncontroller.permission.data.FullStoragePermissionAppsLiveData.FullStoragePackageState; import com.android.permissioncontroller.permission.model.AppPermissionGroup; import com.android.permissioncontroller.permission.model.AppPermissions; -import com.android.permissioncontroller.permission.ui.AdvancedConfirmDialogArgs; import com.android.permissioncontroller.permission.ui.GrantPermissionsViewHandler; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonState; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ButtonType; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel.ChangeRequest; import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModelFactory; +import com.android.permissioncontroller.permission.ui.v33.AdvancedConfirmDialogArgs; import com.android.permissioncontroller.permission.utils.KotlinUtils; import com.android.permissioncontroller.permission.utils.Utils; diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/AdvancedConfirmDialogArgs.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/v33/AdvancedConfirmDialogArgs.kt index 6b26c7e47..b841f3aeb 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/ui/AdvancedConfirmDialogArgs.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/v33/AdvancedConfirmDialogArgs.kt @@ -1,28 +1,27 @@ /* * Copyright (C) 2022 The Android Open Source Project * - * Licensed under the Apache License; - * Version 2.0 (the "License"); + * 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. + * 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.permission.ui +package com.android.permissioncontroller.permission.ui.v33 +import android.os.Build import androidx.annotation.DrawableRes +import androidx.annotation.RequiresApi import androidx.annotation.StringRes import com.android.permissioncontroller.permission.ui.model.AppPermissionViewModel +@RequiresApi(Build.VERSION_CODES.TIRAMISU) data class AdvancedConfirmDialogArgs( @DrawableRes val iconId: Int = 0, @StringRes val titleId: Int = 0, diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/CollectionUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/CollectionUtils.java index 6f2a3079d..108120ced 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/CollectionUtils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/CollectionUtils.java @@ -16,8 +16,6 @@ package com.android.permissioncontroller.permission.utils; -import android.util.ArraySet; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -59,29 +57,6 @@ public final class CollectionUtils { } /** - * Remove all values in the array set that do <b>not</b> exist in the given collection. - * - * @param <T> the class of the elements to retain and of the {@code ArraySet} - * @param arraySet the {@code ArraySet} whose elements are to be removed or retained - * @param valuesToRetain the values to be used to determine which elements to retain - * - * @return {@code true} if any values were removed from the array set, {@code false} otherwise. - * - * @see ArraySet#retainAll(java.util.Collection) - */ - @SafeVarargs - public static <T> boolean retainAll(ArraySet<T> arraySet, T... valuesToRetain) { - boolean removed = false; - for (int i = arraySet.size() - 1; i >= 0; i--) { - if (!ArrayUtils.contains(valuesToRetain, arraySet.valueAt(i))) { - arraySet.removeAt(i); - removed = true; - } - } - return removed; - } - - /** * Return a singleton list containing the element, or an empty list if the element is * {@code null}. * diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt index c4355b1e5..b06a09b28 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/PermissionMapping.kt @@ -26,11 +26,7 @@ import android.util.Log import com.android.modules.utils.build.SdkLevel import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup -import com.android.permission.safetylabel.DataCategory import com.android.permission.safetylabel.DataCategoryConstants -import com.android.permission.safetylabel.DataType -import com.android.permission.safetylabel.DataTypeConstants -import com.android.permission.safetylabel.SafetyLabel /** * This file contains the canonical mapping of permission to permission group, used in the @@ -187,13 +183,6 @@ object PermissionMapping { Manifest.permission_group.SENSORS } - if (SdkLevel.isAtLeastU()) { - PLATFORM_PERMISSIONS[Utils.BODY_SENSORS_WRIST_TEMPERATURE] = - Manifest.permission_group.SENSORS - PLATFORM_PERMISSIONS[Utils.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND] = - Manifest.permission_group.SENSORS - } - for ((permission, permissionGroup) in PLATFORM_PERMISSIONS) { PLATFORM_PERMISSION_GROUPS.getOrPut(permissionGroup) { mutableListOf() }.add(permission) } @@ -390,38 +379,6 @@ object PermissionMapping { return AppOpsManager.opToPermission(opName)?.let { getGroupOfPlatformPermission(it) } } - /* - * Get the sharing purposes for a SafetyLabel related to a specific permission group. - */ - @JvmStatic - fun getSafetyLabelSharingPurposesForGroup( - safetyLabel: SafetyLabel, - groupName: String - ): Set<Int> { - val purposeSet = mutableSetOf<Int>() - val categoriesForPermission = getDataCategoriesForPermissionGroup(groupName) - categoriesForPermission.forEach categoryLoop@{ category -> - val dataCategory: DataCategory? = safetyLabel.dataLabel.dataShared[category] - if (dataCategory == null) { - // Continue to next - return@categoryLoop - } - val typesForCategory = DataTypeConstants.getValidDataTypesForCategory(category) - typesForCategory.forEach typeLoop@{ type -> - val dataType: DataType? = dataCategory.dataTypes[type] - if (dataType == null) { - // Continue to next - return@typeLoop - } - if (dataType.purposeSet.isNotEmpty()) { - purposeSet.addAll(dataType.purposeSet) - } - } - } - - return purposeSet - } - /** * Get the SafetyLabel categories pertaining to a specified permission group. * diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java index 7ccdd6465..d4354bd72 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/Utils.java @@ -76,7 +76,6 @@ import android.hardware.SensorPrivacyManager; import android.os.Binder; import android.os.Build; import android.os.Parcelable; -import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; @@ -110,8 +109,6 @@ import com.android.permissioncontroller.permission.model.AppPermissionGroup; import com.android.permissioncontroller.permission.model.livedatatypes.LightAppPermGroup; import com.android.permissioncontroller.permission.model.livedatatypes.LightPackageInfo; -import kotlin.Triple; - import java.lang.annotation.Retention; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; @@ -123,6 +120,8 @@ import java.util.Locale; import java.util.Random; import java.util.Set; +import kotlin.Triple; + public final class Utils { @Retention(SOURCE) @@ -253,12 +252,6 @@ public final class Utils { private static final String SYSTEM_VISUAL_INTELLIGENCE = "android.app.role.SYSTEM_VISUAL_INTELLIGENCE"; - public static final String BODY_SENSORS_WRIST_TEMPERATURE = - "android.permission.BODY_SENSORS_WRIST_TEMPERATURE"; - - public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = - "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"; - // TODO: theianchen Using hardcoded values here as a WIP solution for now. private static final String[] EXEMPTED_ROLES = { SYSTEM_AMBIENT_AUDIO_INTELLIGENCE, @@ -660,10 +653,6 @@ public final class Utils { return typedValue.resourceId; } - public static List<ApplicationInfo> getAllInstalledApplications(Context context) { - return context.getPackageManager().getInstalledApplications(0); - } - /** * Is the group or background group user sensitive? * @@ -1073,38 +1062,6 @@ public final class Utils { } /** - * Checks whether a package has an active one-time permission according to the system server's - * flags - * - * @param context the {@code Context} to retrieve {@code PackageManager} - * @param packageName The package to check for - * @return Whether a package has an active one-time permission - */ - public static boolean hasOneTimePermissions(Context context, String packageName) { - String[] permissions; - PackageManager pm = context.getPackageManager(); - try { - permissions = pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) - .requestedPermissions; - } catch (NameNotFoundException e) { - Log.w(LOG_TAG, "Checking for one-time permissions in nonexistent package"); - return false; - } - if (permissions == null) { - return false; - } - for (String permissionName : permissions) { - if ((pm.getPermissionFlags(permissionName, packageName, Process.myUserHandle()) - & PackageManager.FLAG_PERMISSION_ONE_TIME) != 0 - && pm.checkPermission(permissionName, packageName) - == PackageManager.PERMISSION_GRANTED) { - return true; - } - } - return false; - } - - /** * Returns a random session ID value that's guaranteed to not be {@code INVALID_SESSION_ID}. * * @return A valid session ID. diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/AdminRestrictedPermissionsUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java index 4944093ac..9b7927c1d 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/AdminRestrictedPermissionsUtils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/AdminRestrictedPermissionsUtils.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.permissioncontroller.permission.utils; +package com.android.permissioncontroller.permission.utils.v31; import android.Manifest; import android.app.admin.DevicePolicyManager; @@ -24,6 +24,7 @@ import android.os.UserManager; import android.util.ArraySet; import com.android.modules.utils.build.SdkLevel; +import com.android.permissioncontroller.permission.utils.Utils; /** * A class for dealing with permissions that the admin may not grant in certain configurations. @@ -52,13 +53,6 @@ public final class AdminRestrictedPermissionsUtils { if (SdkLevel.isAtLeastT()) { ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); } - // New U permissions - do not add unless running on U and above. - if (SdkLevel.isAtLeastU()) { - ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add( - Utils.BODY_SENSORS_WRIST_TEMPERATURE); - ADMIN_RESTRICTED_SENSORS_PERMISSIONS.add( - Utils.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND); - } } /** diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java b/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/SubattributionUtils.java index 2785eca74..8919953b4 100644 --- a/PermissionController/src/com/android/permissioncontroller/permission/utils/SubattributionUtils.java +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/v31/SubattributionUtils.java @@ -14,9 +14,8 @@ * limitations under the License. */ -package com.android.permissioncontroller.permission.utils; +package com.android.permissioncontroller.permission.utils.v31; -import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.Attribution; @@ -25,6 +24,7 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.Build; +import androidx.annotation.ChecksSdkIntAtLeast; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -42,6 +42,7 @@ public class SubattributionUtils { /** * Returns true if the app supports subattribution. */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) public static boolean isSubattributionSupported(Context context, ApplicationInfo appInfo) { if (!SdkLevel.isAtLeastS()) { return false; @@ -50,6 +51,7 @@ public class SubattributionUtils { } /** Returns whether the provided package supports subattribution. */ + @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) public static boolean isSubattributionSupported(LightPackageInfo lightPackageInfo) { return SdkLevel.isAtLeastS() && lightPackageInfo.getAreAttributionsUserVisible(); } @@ -59,7 +61,6 @@ public class SubattributionUtils { * {@code null} otherwise. */ @Nullable - @SuppressLint("NewApi") // isSubattributionSupported checks api level public static Map<Integer, String> getAttributionLabels(Context context, PackageInfo pkgInfo) { if (!isSubattributionSupported(context, pkgInfo.applicationInfo)) { return null; @@ -72,7 +73,6 @@ public class SubattributionUtils { * {@code null} otherwise. */ @Nullable - @SuppressLint("NewApi") // isSubattributionSupported checks api level public static Map<Integer, String> getAttributionLabels(Context context, ApplicationInfo appInfo) { if (!isSubattributionSupported(context, appInfo)) { @@ -114,7 +114,6 @@ public class SubattributionUtils { /** Returns the attribution label map for the package if the app supports subattribution. */ @Nullable - @SuppressLint("NewApi") // isSubattributionSupported checks api level public static Map<Integer, String> getAttributionLabels(Context context, LightPackageInfo lightPackageInfo) { if (!isSubattributionSupported(lightPackageInfo)) { diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/v34/SafetyLabelUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/v34/SafetyLabelUtils.kt new file mode 100644 index 000000000..5dbe203f9 --- /dev/null +++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/v34/SafetyLabelUtils.kt @@ -0,0 +1,58 @@ +/* + * 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.permission.utils.v34 + +import com.android.permission.safetylabel.DataCategory +import com.android.permission.safetylabel.DataType +import com.android.permission.safetylabel.DataTypeConstants +import com.android.permission.safetylabel.SafetyLabel +import com.android.permissioncontroller.permission.utils.PermissionMapping + +object SafetyLabelUtils { + /* + * Get the sharing purposes for a SafetyLabel related to a specific permission group. + */ + @JvmStatic + fun getSafetyLabelSharingPurposesForGroup( + safetyLabel: SafetyLabel, + groupName: String + ): Set<Int> { + val purposeSet = mutableSetOf<Int>() + val categoriesForPermission = PermissionMapping + .getDataCategoriesForPermissionGroup(groupName) + categoriesForPermission.forEach categoryLoop@{ category -> + val dataCategory: DataCategory? = safetyLabel.dataLabel.dataShared[category] + if (dataCategory == null) { + // Continue to next + return@categoryLoop + } + val typesForCategory = DataTypeConstants.getValidDataTypesForCategory(category) + typesForCategory.forEach typeLoop@{ type -> + val dataType: DataType? = dataCategory.dataTypes[type] + if (dataType == null) { + // Continue to next + return@typeLoop + } + if (dataType.purposeSet.isNotEmpty()) { + purposeSet.addAll(dataType.purposeSet) + } + } + } + + return purposeSet + } +} diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt index 11881e3ab..4ba685589 100644 --- a/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt +++ b/PermissionController/src/com/android/permissioncontroller/privacysources/AccessibilitySourceService.kt @@ -420,7 +420,7 @@ class AccessibilitySourceService( // Start this Settings activity using the same UX that settings slices uses. This allows // settings to correctly support 2-pane layout with as-best-as-possible transition // animation. - intent.putExtra(EXTRA_IS_FROM_SLICE, true) + intent.putExtra(Constants.EXTRA_IS_FROM_SLICE, true) return PendingIntent.getActivity( context, 0, @@ -649,7 +649,6 @@ class AccessibilitySourceService( private const val PROPERTY_SC_ACCESSIBILITY_JOB_INTERVAL_MILLIS = "sc_accessibility_job_interval_millis" private val DEFAULT_SC_ACCESSIBILITY_JOB_INTERVAL_MILLIS = TimeUnit.DAYS.toMillis(1) - private val EXTRA_IS_FROM_SLICE = "is_from_slice" private val sourceStateChanged = SafetyEvent.Builder( SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build() diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/SafetyCenterReceiver.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/SafetyCenterReceiver.kt index 874fe942e..885b8ea86 100644 --- a/PermissionController/src/com/android/permissioncontroller/privacysources/SafetyCenterReceiver.kt +++ b/PermissionController/src/com/android/permissioncontroller/privacysources/SafetyCenterReceiver.kt @@ -39,8 +39,6 @@ import com.android.permissioncontroller.permission.utils.Utils import com.android.permissioncontroller.privacysources.WorkPolicyInfo.Companion.WORK_POLICY_INFO_SOURCE_ID import com.android.permissioncontroller.privacysources.v34.AppDataSharingUpdatesPrivacySource import com.android.permissioncontroller.privacysources.v34.AppDataSharingUpdatesPrivacySource.Companion.APP_DATA_SHARING_UPDATES_SOURCE_ID -import com.android.permissioncontroller.privacysources.v34.HealthConnectPrivacySource -import com.android.permissioncontroller.privacysources.v34.HealthConnectPrivacySource.Companion.HEALTH_CONNECT_SOURCE_ID import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.Default @@ -59,7 +57,6 @@ private fun createMapOfSourceIdsToSources(context: Context): Map<String, Privacy if (SdkLevel.isAtLeastU()) { sourceMap[APP_DATA_SHARING_UPDATES_SOURCE_ID] = AppDataSharingUpdatesPrivacySource() - sourceMap[HEALTH_CONNECT_SOURCE_ID] = HealthConnectPrivacySource() } return sourceMap diff --git a/PermissionController/src/com/android/permissioncontroller/privacysources/v34/HealthConnectPrivacySource.kt b/PermissionController/src/com/android/permissioncontroller/privacysources/v34/HealthConnectPrivacySource.kt deleted file mode 100644 index f05b061c7..000000000 --- a/PermissionController/src/com/android/permissioncontroller/privacysources/v34/HealthConnectPrivacySource.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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.privacysources.v34 - -import android.app.PendingIntent -import android.app.PendingIntent.FLAG_IMMUTABLE -import android.app.PendingIntent.FLAG_UPDATE_CURRENT -import android.content.Context -import android.content.Intent -import android.os.Build -import android.safetycenter.SafetyCenterManager -import android.safetycenter.SafetyEvent -import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED -import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED -import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED -import android.safetycenter.SafetySourceData -import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED -import android.safetycenter.SafetySourceStatus -import androidx.annotation.RequiresApi -import com.android.permissioncontroller.R -import com.android.permissioncontroller.permission.utils.Utils -import com.android.permissioncontroller.privacysources.PrivacySource -import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent -import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.EVENT_DEVICE_REBOOTED -import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.EVENT_REFRESH_REQUESTED -import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.UNKNOWN - -/** - * Privacy source providing the Health Connect entry to Safety Center. - * - * The content of the Health Connect is static, however the entry should only be displayed if the - * Health Connect Permission UI Enabled feature is enabled. - */ -@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) -class HealthConnectPrivacySource : PrivacySource { - - override val shouldProcessProfileRequest: Boolean = false - - override fun safetyCenterEnabledChanged(context: Context, enabled: Boolean) { - // Do nothing. - } - - override fun rescanAndPushSafetyCenterData( - context: Context, - intent: Intent, - refreshEvent: RefreshEvent - ) { - val safetyCenterManager: SafetyCenterManager = - Utils.getSystemServiceSafe(context, SafetyCenterManager::class.java) - - val safetySourceData = - if (Utils.isHealthPermissionUiEnabled()) { - SafetySourceData.Builder() - .setStatus( - SafetySourceStatus.Builder( - context.getString(R.string.health_connect_title), - context.getString(R.string.health_connect_summary), - SEVERITY_LEVEL_UNSPECIFIED - ) - .setPendingIntent( - PendingIntent.getActivity( - context, - /* requestCode= */ 0, - Intent(HEALTH_CONNECT_INTENT_ACTION), - FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE - ) - ) - .build(), - ) - .build() - } else { - null - } - - safetyCenterManager.setSafetySourceData( - HEALTH_CONNECT_SOURCE_ID, - safetySourceData, - createSafetyEventForHealthConnect(refreshEvent, intent) - ) - } - - private fun createSafetyEventForHealthConnect( - refreshEvent: RefreshEvent, - intent: Intent - ): SafetyEvent { - return when (refreshEvent) { - EVENT_REFRESH_REQUESTED -> { - val refreshBroadcastId = - intent.getStringExtra( - SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID - ) - SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId(refreshBroadcastId) - .build() - } - EVENT_DEVICE_REBOOTED -> { - SafetyEvent.Builder(SAFETY_EVENT_TYPE_DEVICE_REBOOTED).build() - } - UNKNOWN -> { - SafetyEvent.Builder(SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED).build() - } - } - } - - /** Companion object for [HealthConnectPrivacySource]. */ - companion object { - /** Source id for safety center source for health connect. */ - const val HEALTH_CONNECT_SOURCE_ID = "AndroidHealthConnect" - const val HEALTH_CONNECT_INTENT_ACTION = - "android.health.connect.action.HEALTH_HOME_SETTINGS" - } -} diff --git a/PermissionController/tests/inprocess/Android.bp b/PermissionController/tests/inprocess/Android.bp index 0cc355daa..78c767f1d 100644 --- a/PermissionController/tests/inprocess/Android.bp +++ b/PermissionController/tests/inprocess/Android.bp @@ -33,7 +33,10 @@ android_test { target_sdk_version: "30", min_sdk_version: "30", - srcs: ["src/**/*.kt"], + srcs: [ + "src/**/*.kt", + "src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java", + ], libs: [ "android.test.base", @@ -46,6 +49,7 @@ android_test { "androidx.test.ext.junit", "androidx.test.uiautomator_uiautomator", "compatibility-device-util-axt", + "kotlin-test", "permission-test-util-lib", ], diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java new file mode 100644 index 000000000..b4b18dbbe --- /dev/null +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/compat/LinkMovementMethodCompatTest.java @@ -0,0 +1,258 @@ +/* + * 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.permission.compat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.content.Context; +import android.os.SystemClock; +import android.text.Selection; +import android.text.Spannable; +import android.text.Spanned; +import android.text.method.MovementMethod; +import android.text.style.ClickableSpan; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputConnection; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.test.annotation.UiThreadTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +/** + * Test for {@link LinkMovementMethodCompat} without using Mockito, which is unavailable for + * in-process tests. + * + * @see android.text.method.cts.LinkMovementMethodTest + */ +public class LinkMovementMethodCompatTest { + private static final String CONTENT = "clickable\nunclickable\nclickable"; + + private Activity mActivity; + private LinkMovementMethodCompat mMethod; + private TextView mView; + private Spannable mSpannable; + private MockClickableSpan mClickable0; + private MockClickableSpan mClickable1; + + @Rule + public ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class); + + @Before + public void setup() throws Throwable { + mActivity = mActivityRule.getActivity(); + mMethod = new LinkMovementMethodCompat(); + + // Set the content view with a text view which contains 3 lines, + mActivityRule.runOnUiThread(() -> mView = new TextViewNoIme(mActivity)); + mView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10); + mView.setText(CONTENT, TextView.BufferType.SPANNABLE); + + mActivityRule.runOnUiThread(() -> mActivity.setContentView(mView)); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + mSpannable = (Spannable) mView.getText(); + // make first line clickable + mClickable0 = markClickable(0, CONTENT.indexOf('\n')); + // make last line clickable + mClickable1 = markClickable(CONTENT.lastIndexOf('\n'), CONTENT.length()); + } + + @Test + public void testConstructor() { + new LinkMovementMethodCompat(); + } + + @Test + public void testGetInstance() { + MovementMethod method0 = LinkMovementMethodCompat.getInstance(); + assertTrue(method0 instanceof LinkMovementMethodCompat); + + MovementMethod method1 = LinkMovementMethodCompat.getInstance(); + assertNotNull(method1); + assertSame(method0, method1); + } + + @UiThreadTest + @Test + public void testOnTouchEvent() { + assertSelection(mSpannable, -1); + + // press on first line (Clickable) + assertTrue(pressOnLine(0)); + assertSelectClickableLeftToRight(mSpannable, mClickable0); + + // release on first line + mClickable0.clearClickCount(); + assertTrue(releaseOnLine(0)); + mClickable0.assertClickCount(1); + + // press on second line (unclickable) + assertSelectClickableLeftToRight(mSpannable, mClickable0); + // just clear selection + pressOnLine(1); + assertSelection(mSpannable, -1); + + // press on last line (Clickable) + assertTrue(pressOnLine(2)); + assertSelectClickableLeftToRight(mSpannable, mClickable1); + + // release on last line + mClickable1.clearClickCount(); + assertTrue(releaseOnLine(2)); + mClickable1.assertClickCount(1); + + // release on second line (unclickable) + assertSelectClickableLeftToRight(mSpannable, mClickable1); + // just clear selection + releaseOnLine(1); + assertSelection(mSpannable, -1); + } + + @UiThreadTest + @Test + public void testOnTouchEvent_outsideLineBounds() { + assertSelection(mSpannable, -1); + + // press on first line (clickable) + assertTrue(pressOnLine(0)); + assertSelectClickableLeftToRight(mSpannable, mClickable0); + + // release above first line + mClickable0.clearClickCount(); + float x = (mView.getLayout().getLineLeft(0) + mView.getLayout().getLineRight(0)) / 2f; + float y = -1f; + assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP)); + mClickable0.assertClickCount(0); + + // press on first line (clickable) + assertTrue(pressOnLine(0)); + assertSelectClickableLeftToRight(mSpannable, mClickable0); + + // release to left of first line + mClickable0.clearClickCount(); + x = mView.getLayout().getLineLeft(0) - 1f; + y = (mView.getLayout().getLineTop(0) + mView.getLayout().getLineBottom(0)) / 2f; + assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP)); + mClickable0.assertClickCount(0); + + // press on first line (clickable) + assertTrue(pressOnLine(0)); + assertSelectClickableLeftToRight(mSpannable, mClickable0); + + // release to right of first line + mClickable0.clearClickCount(); + x = mView.getLayout().getLineRight(0) + 1f; + y = (mView.getLayout().getLineTop(0) + mView.getLayout().getLineBottom(0)) / 2f; + assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP)); + mClickable0.assertClickCount(0); + + // press on last line (clickable) + assertTrue(pressOnLine(2)); + assertSelectClickableLeftToRight(mSpannable, mClickable1); + + // release below last line + mClickable1.clearClickCount(); + x = (mView.getLayout().getLineLeft(0) + mView.getLayout().getLineRight(0)) / 2f; + y = mView.getLayout().getHeight() + 1f; + assertFalse(performMotionAtPoint(x, y, MotionEvent.ACTION_UP)); + mClickable1.assertClickCount(0); + } + + private MockClickableSpan markClickable(final int start, final int end) throws Throwable { + final MockClickableSpan clickableSpan = new MockClickableSpan(); + mActivityRule.runOnUiThread(() -> mSpannable.setSpan(clickableSpan, start, end, + Spanned.SPAN_MARK_MARK)); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + return clickableSpan; + } + private boolean performMotionAtPoint(float x, float y, int action) { + long now = SystemClock.uptimeMillis(); + return mMethod.onTouchEvent(mView, mSpannable, + MotionEvent.obtain(now, now, action, x, y, 0)); + } + + private boolean performMotionOnLine(int line, int action) { + float x = (mView.getLayout().getLineLeft(line) + mView.getLayout().getLineRight(line)) / 2f; + float y = (mView.getLayout().getLineTop(line) + mView.getLayout().getLineBottom(line)) / 2f; + return performMotionAtPoint(x, y, action); + } + + private boolean pressOnLine(int line) { + return performMotionOnLine(line, MotionEvent.ACTION_DOWN); + } + + private boolean releaseOnLine(int line) { + return performMotionOnLine(line, MotionEvent.ACTION_UP); + } + + private void assertSelection(Spannable spannable, int start, int end) { + assertEquals(start, Selection.getSelectionStart(spannable)); + assertEquals(end, Selection.getSelectionEnd(spannable)); + } + + private void assertSelection(Spannable spannable, int position) { + assertSelection(spannable, position, position); + } + + private void assertSelectClickableLeftToRight(Spannable spannable, + ClickableSpan clickableSpan) { + assertSelection(spannable, spannable.getSpanStart(clickableSpan), + spannable.getSpanEnd(clickableSpan)); + } + + public static class TextViewNoIme extends TextView { + public TextViewNoIme(@NonNull Context context) { + super(context); + } + + @Override + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { + return null; + } + } + + public static class MockClickableSpan extends ClickableSpan { + private int mClickCount = 0; + + @Override + public void onClick(@NonNull View widget) { + ++mClickCount; + } + + public void assertClickCount(int expectedClickCount) { + assertEquals(expectedClickCount, mClickCount); + } + + public void clearClickCount() { + mClickCount = 0; + } + } +} diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt new file mode 100644 index 000000000..305dcdfb7 --- /dev/null +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/ArrayUtilsTest.kt @@ -0,0 +1,74 @@ +/* + * 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.permission.util + +import com.android.permissioncontroller.permission.utils.ArrayUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class ArrayUtilsTest { + @Test + fun appendString_appendToNull_returnsArrayWithString() { + assertThat(ArrayUtils.appendString(null, TEST_STRING)) + .isEqualTo(arrayOf(TEST_STRING)) + } + + @Test + fun appendString_appendToNull_returnsArrayWithNull() { + val result = ArrayUtils.appendString(null, null) + assertThat(result.size).isEqualTo(1) + assertThat(result[0]).isEqualTo(null) + } + + @Test + fun appendString_duplicatedString_returnsArray() { + val cur = arrayOf("a", "b", TEST_STRING) + assertThat(ArrayUtils.appendString(cur, TEST_STRING)).isEqualTo(cur) + } + + @Test + fun appendString_appendNull_returnsArray() { + val cur = arrayOf("a", "b", null) + assertThat(ArrayUtils.appendString(cur, null)).isEqualTo(cur) + } + + @Test + fun appendString_appendToEmptyArray_returnsArrayWithNewString() { + val cur = arrayOf<String>() + val new = arrayOf(TEST_STRING) + assertThat(ArrayUtils.appendString(cur, TEST_STRING)).isEqualTo(new) + } + + @Test + fun appendString_appendNullToEmptyArray_returnsArrayWithNewString() { + val cur = arrayOf<String>() + val result = ArrayUtils.appendString(cur, null) + assertThat(result.size).isEqualTo(1) + assertThat(result[0]).isNull() + } + + @Test + fun appendString_appendNewString() { + val cur = arrayOf("old test") + val new = arrayOf("old test", TEST_STRING) + assertThat(ArrayUtils.appendString(cur, TEST_STRING)).isEqualTo(new) + } + + companion object { + private const val TEST_STRING = "test" + } +} diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt new file mode 100644 index 000000000..bfcb67166 --- /dev/null +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/CollectionUtilsTest.kt @@ -0,0 +1,60 @@ +/* + * 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.permission.util + +import com.android.permissioncontroller.permission.utils.CollectionUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class CollectionUtilsTest { + @Test + fun testContains_true() { + val byteArrays = setOf(TEST_BYTE_ARRAY_1, TEST_BYTE_ARRAY_2, TEST_BYTE_ARRAY_3) + + assertThat(CollectionUtils.contains(byteArrays, TEST_BYTE_ARRAY_1)).isTrue() + } + + @Test + fun testContains_false() { + val byteArrays = setOf(TEST_BYTE_ARRAY_1, TEST_BYTE_ARRAY_2, TEST_BYTE_ARRAY_3) + + assertThat(CollectionUtils.contains(byteArrays, TEST_BYTE_ARRAY_4)).isFalse() + } + + @Test + fun testContainsSubset_true() { + val byteArrays = setOf(TEST_BYTE_ARRAY_1, TEST_BYTE_ARRAY_2, TEST_BYTE_ARRAY_3) + val otherByteArrays = setOf(TEST_BYTE_ARRAY_2, TEST_BYTE_ARRAY_3) + + assertThat(CollectionUtils.containsSubset(byteArrays, otherByteArrays)).isTrue() + } + + @Test + fun testContainsSubset_false() { + val byteArrays = setOf(TEST_BYTE_ARRAY_1, TEST_BYTE_ARRAY_2, TEST_BYTE_ARRAY_3) + val otherByteArrays = setOf(TEST_BYTE_ARRAY_3, TEST_BYTE_ARRAY_4) + + assertThat(CollectionUtils.containsSubset(byteArrays, otherByteArrays)).isFalse() + } + + companion object { + private val TEST_BYTE_ARRAY_1 = "I".toByteArray() + private val TEST_BYTE_ARRAY_2 = "love".toByteArray() + private val TEST_BYTE_ARRAY_3 = "Google".toByteArray() + private val TEST_BYTE_ARRAY_4 = "Hello".toByteArray() + } +} diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt new file mode 100644 index 000000000..8f54da579 --- /dev/null +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/KotlinUtilsTest.kt @@ -0,0 +1,165 @@ +/* + * 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.permission.util + +import android.Manifest.permission.READ_MEDIA_IMAGES +import android.Manifest.permission.READ_MEDIA_VIDEO +import android.content.Context +import android.content.Intent +import android.content.Intent.ACTION_SHOW_APP_INFO +import android.content.Intent.EXTRA_PACKAGE_NAME +import android.content.pm.ActivityInfo +import android.content.pm.PackageManager +import android.content.pm.ResolveInfo +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.permissioncontroller.permission.utils.KotlinUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mockito.argThat +import org.mockito.Mockito.mock +import kotlin.test.assertFailsWith +import org.mockito.Mockito.`when` as whenever + +/** + * Unit tests for [KotlinUtils]. + */ +@RunWith(AndroidJUnit4::class) +class KotlinUtilsTest { + private val targetContext = InstrumentationRegistry.getInstrumentation().targetContext + + @Test + fun convertToBitmap_argb888BitmapDrawable_returnsSameBitmap() { + val bitmap = Bitmap.createBitmap(/* width= */ 5, /* height= */ 10, Bitmap.Config.ARGB_8888) + val drawable = BitmapDrawable(targetContext.resources, bitmap) + + assertThat(KotlinUtils.convertToBitmap(drawable).sameAs(bitmap)).isTrue() + } + + @Test + fun convertToBitmap_noIntrinsicSize_throws() { + val drawable = FakeDrawable(intrinsicSize = 0) + assertFailsWith<IllegalArgumentException> { KotlinUtils.convertToBitmap(drawable) } + } + + class FakeDrawable(private val intrinsicSize: Int) : Drawable() { + override fun getIntrinsicWidth() = intrinsicSize + override fun getIntrinsicHeight() = intrinsicSize + + override fun draw(canvas: Canvas) = Unit // no-op + override fun getOpacity() = throw UnsupportedOperationException() + override fun setAlpha(alpha: Int) = throw UnsupportedOperationException() + override fun setColorFilter(colorFilter: ColorFilter?) = + throw UnsupportedOperationException() + } + + @Test + fun getAppStoreIntent_returnsResolvedIntent() { + val installerPackage = "installer" + val installerActivity = "activity" + val appPackage = "app" + val mockContext = mock(Context::class.java) + val mockPackageManager = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(mockPackageManager) + val installerIntent = Intent(ACTION_SHOW_APP_INFO).setPackage(installerPackage) + whenever( + mockPackageManager.resolveActivity( + argThat { intent -> intent.filterEquals(installerIntent) }, /* flags= */ anyInt())) + .thenReturn(ResolveInfo().apply { + activityInfo = ActivityInfo().apply { + packageName = installerPackage + name = installerActivity + } + }) + + val intent = KotlinUtils.getAppStoreIntent(mockContext, installerPackage, appPackage) + + assertThat(intent).isNotNull() + with (intent!!) { + assertThat(action).isEqualTo(ACTION_SHOW_APP_INFO) + assertThat(component?.packageName).isEqualTo(installerPackage) + assertThat(component?.className).isEqualTo(installerActivity) + } + } + + @Test + fun getAppStoreIntent_returnsAppPackageInExtras() { + val appPackage = "app" + val mockContext = mock(Context::class.java) + val mockPackageManager = mock(PackageManager::class.java) + whenever(mockContext.packageManager).thenReturn(mockPackageManager) + whenever( + mockPackageManager.resolveActivity(any(), /* flags= */ anyInt())) + .thenReturn(ResolveInfo().apply { + activityInfo = ActivityInfo().apply { + packageName = "" + name = "" + } + }) + + val intent = KotlinUtils.getAppStoreIntent(mockContext, "com.installer", appPackage) + + assertThat(intent).isNotNull() + assertThat(intent?.extras?.getString(EXTRA_PACKAGE_NAME)).isEqualTo(appPackage) + } + + @Test + fun getAppStoreIntent_returnsNullWhenInstallerNotResolved() { + val mockContext = mock(Context::class.java) + whenever(mockContext.packageManager).thenReturn(mock(PackageManager::class.java)) + // Un-stubbed activity resolution will return null. + + assertThat(KotlinUtils.getAppStoreIntent(mockContext, "com.installer", "com.app")).isNull() + } + + @Test + fun getMimeTypeForPermissions_onlyReadMediaImages_returnsImage() { + assertThat(KotlinUtils.getMimeTypeForPermissions(listOf(READ_MEDIA_IMAGES, "read memes"))) + .isEqualTo("image/*") + } + + @Test + fun getMimeTypeForPermissions_onlyReadMediaVideo_returnsVideo() { + assertThat(KotlinUtils.getMimeTypeForPermissions(listOf("write memes", READ_MEDIA_VIDEO))) + .isEqualTo("video/*") + } + + @Test + fun getMimeTypeForPermissions_bothReadMediaPermissions_returnsNull() { + assertThat( + KotlinUtils.getMimeTypeForPermissions(listOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO))) + .isNull() + } + + @Test + fun getMimeTypeForPermissions_noReadMediaPermissions_returnsNull() { + assertThat(KotlinUtils.getMimeTypeForPermissions(listOf("amazing permission"))).isNull() + } + + @Test + fun getMimeTypeForPermissions_emptyList_returnsNull() { + assertThat(KotlinUtils.getMimeTypeForPermissions(emptyList())).isNull() + } +} diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt new file mode 100644 index 000000000..64a13df60 --- /dev/null +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/PermissionMappingTest.kt @@ -0,0 +1,59 @@ +/* + * 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.permission.util + +import android.Manifest +import android.app.AppOpsManager +import android.health.connect.HealthPermissions +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.permissioncontroller.permission.utils.PermissionMapping +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PermissionMappingTest { + @Test + fun testGetPlatformPermissionGroupForOp_healthPermissionGroup() { + assertThat(PermissionMapping.getPlatformPermissionGroupForOp( + AppOpsManager.OPSTR_READ_WRITE_HEALTH_DATA + )).isEqualTo(HealthPermissions.HEALTH_PERMISSION_GROUP) + } + + @Test + fun testGetPlatformPermissionGroupForOp_microphone() { + assertThat(PermissionMapping.getPlatformPermissionGroupForOp( + AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE + )).isEqualTo(Manifest.permission_group.MICROPHONE) + } + + @Test + fun testGetPlatformPermissionGroupForOp_camera() { + assertThat( + PermissionMapping.getPlatformPermissionGroupForOp(AppOpsManager.OPSTR_PHONE_CALL_CAMERA) + ).isEqualTo(Manifest.permission_group.CAMERA) + } + + @Test + fun testGetPlatformPermissionGroupForOp_readContacts() { + assertThat( + PermissionMapping.getPlatformPermissionGroupForOp(AppOpsManager.OPSTR_READ_CONTACTS) + ).isEqualTo( + PermissionMapping.getGroupOfPlatformPermission(Manifest.permission.READ_CONTACTS) + ) + } +} diff --git a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt index c53f94195..15218024e 100644 --- a/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt +++ b/PermissionController/tests/inprocess/src/com/android/permissioncontroller/permission/util/UtilsTest.kt @@ -17,23 +17,201 @@ package com.android.permissioncontroller.permission.util import android.Manifest.permission.READ_CONTACTS +import android.Manifest.permission_group.ACTIVITY_RECOGNITION +import android.Manifest.permission_group.CALENDAR +import android.Manifest.permission_group.CALL_LOG +import android.Manifest.permission_group.CAMERA import android.Manifest.permission_group.CONTACTS +import android.Manifest.permission_group.LOCATION +import android.Manifest.permission_group.MICROPHONE +import android.Manifest.permission_group.NEARBY_DEVICES +import android.Manifest.permission_group.PHONE +import android.Manifest.permission_group.READ_MEDIA_AURAL +import android.Manifest.permission_group.READ_MEDIA_VISUAL +import android.Manifest.permission_group.SENSORS +import android.Manifest.permission_group.SMS +import android.Manifest.permission_group.STORAGE +import android.Manifest.permission_group.UNDEFINED +import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.SharedPreferences +import android.content.pm.PackageManager.NameNotFoundException import android.content.res.Resources import androidx.test.platform.app.InstrumentationRegistry import com.android.permissioncontroller.Constants.EXTRA_SESSION_ID import com.android.permissioncontroller.Constants.INVALID_SESSION_ID +import com.android.permissioncontroller.R import com.android.permissioncontroller.permission.utils.Utils +import com.android.permissioncontroller.privacysources.WorkPolicyInfo import com.google.common.truth.Truth.assertThat +import org.junit.Ignore import org.junit.Test - +import kotlin.test.assertFailsWith class UtilsTest { - private val context = InstrumentationRegistry.getInstrumentation().context as Context + private val context = InstrumentationRegistry.getInstrumentation().targetContext as Context + + @Test + fun getAbsoluteTimeString_zero_returnsNull() { + assertThat(Utils.getAbsoluteTimeString(context, 0)).isNull() + } + + @Test + fun getAbsoluteTimeString_currentTime_returnsTimeFormatString() { + val time = Utils.getAbsoluteTimeString(context, System.currentTimeMillis()) + assertThat(time).isNotNull() + if (time != null) { + if (time.contains(":")) { + val times = time.split(":") + assertThat(times.size).isEqualTo(2) + val isTime = times[1].contains("am", true) || times[1].contains("pm", true) + assertThat(isTime).isTrue() + } else { + assertThat(time.contains(".")).isTrue() + val times = time.split(".") + assertThat(times.size).isEqualTo(3) + } + } + } + + @Test + fun getAbsoluteTimeString_previousDateTime_returnsDateFormatString() { + val lastAccessTime = 1680739840723L + val time = Utils.getAbsoluteTimeString(context, lastAccessTime) + assertThat(time == "Apr 5, 2023" || time == "Apr 6, 2023").isTrue() + } + + @Test + fun getBlockedIcon_invalidGroupName_returnsMinusOne() { + assertThat(Utils.getBlockedIcon(INVALID_GROUP_NAME)).isEqualTo(-1) + } + + @Test + fun getBlockedIcon_validGroupName() { + assertThat(Utils.getBlockedIcon(CAMERA)).isEqualTo(R.drawable.ic_camera_blocked) + } + + @Test + fun getBlockedTitle_invalidGroupName_returnsMinusOne() { + assertThat(Utils.getBlockedTitle(INVALID_GROUP_NAME)).isEqualTo(-1) + } + @Test + fun getBlockedTitle_validGroupName() { + assertThat(Utils.getBlockedTitle(CAMERA)).isEqualTo(R.string.blocked_camera_title) + } + + @Test + fun getDeviceProtectedSharedPreferences() { + assertThat(Utils.getDeviceProtectedSharedPreferences(context)) + .isInstanceOf(SharedPreferences::class.java) + } + + @Test + @Ignore("b/277782895") + fun getEnterpriseString() { + assertThat(Utils.getEnterpriseString(context, WorkPolicyInfo.WORK_POLICY_TITLE, + R.string.work_policy_title)).isInstanceOf(String::class.java) + } + + @Test + fun getOneTimePermissionsTimeout_returnsNonNegativeTimeout() { + assertThat(Utils.getOneTimePermissionsTimeout()).isGreaterThan(0L) + } + + @Test + fun getOneTimePermissionsKilledDelaySelfRevoked() { + assertThat(Utils.getOneTimePermissionsKilledDelay(true)).isEqualTo(0) + } + + @Test + fun getOneTimePermissionsKilledDelayNonSelfRevoked() { + assertThat(Utils.getOneTimePermissionsKilledDelay(false)).isAtLeast(0L) + } @Test - fun assertGetOrGenerateSessionIdExpectedSessionId() { + fun getPackageInfoForComponentName_NonExistComponent_throwsNameNotFoundException() { + val testComponent = ComponentName("com.test.package", "TestClass") + assertFailsWith<NameNotFoundException> { + Utils.getPackageInfoForComponentName(context, testComponent) + } + } + + @Test + fun getPermissionGroupDescriptionString_undefinedPermissionGroup() { + val description = "test permission group description" + val resultString = + context.getString(R.string.permission_description_summary_generic, description) + assertThat(Utils.getPermissionGroupDescriptionString(context, UNDEFINED, description)) + .isEqualTo(resultString) + } + + @Test + fun getPermissionGroupDescriptionString_validPermissionGroup() { + val permissionGroupNames = listOf(ACTIVITY_RECOGNITION, CALENDAR, CALL_LOG, + CAMERA, CONTACTS, LOCATION, MICROPHONE, NEARBY_DEVICES, PHONE, READ_MEDIA_AURAL, + READ_MEDIA_VISUAL, SENSORS, SMS, STORAGE) + for (permissionGroupName in permissionGroupNames) { + assertThat(Utils.getPermissionGroupDescriptionString(context, permissionGroupName, "")) + .isNotNull() + } + } + + @Test + fun getPermissionLastAccessSummaryTimestamp_lastAccessSummaryTimestampIsNull() { + val result = Utils.getPermissionLastAccessSummaryTimestamp(null, context, LOCATION) + assertThat(result.first).isEqualTo("") + assertThat(result.second).isEqualTo(Utils.NOT_IN_LAST_7D) + assertThat(result.third).isEqualTo("") + } + + @Test + fun getPermissionLastAccessSummaryTimestamp_sensorDataPermission_lastAccessSummaryTimestampIsToday() { + val result = Utils.getPermissionLastAccessSummaryTimestamp(System.currentTimeMillis(), + context, LOCATION) + assertThat(result.first).isNotEmpty() + assertThat(result.second).isEqualTo(Utils.LAST_24H_SENSOR_TODAY) + assertThat(result.third).isNotEmpty() + } + + @Test + fun getPermissionLastAccessSummaryTimestamp_sensorDataPermission_lastAccessSummaryTimestampIsYesterday() { + val result = Utils.getPermissionLastAccessSummaryTimestamp( + System.currentTimeMillis() - 24 * 60 * 60 * 1000, context, LOCATION) + assertThat(result.first).isNotEmpty() + assertThat(result.second).isEqualTo(Utils.LAST_24H_SENSOR_YESTERDAY) + assertThat(result.third).isNotEmpty() + } + + @Test + fun getPermissionLastAccessSummaryTimestamp_sensorDataPermission_lastAccessSummaryTimestampIsLast7Days() { + val result = Utils.getPermissionLastAccessSummaryTimestamp( + System.currentTimeMillis() - 5 * 24 * 60 * 60 * 1000, context, LOCATION) + assertThat(result.first).isNotEmpty() + assertThat(result.second).isEqualTo(Utils.LAST_7D_SENSOR) + assertThat(result.third).isNotEmpty() + } + + @Test + fun getPermissionLastAccessSummaryTimestamp_nonSensorDataPermission_lastAccessSummaryTimestampIsLast24Hrs() { + val result = Utils.getPermissionLastAccessSummaryTimestamp( + System.currentTimeMillis(), context, STORAGE) + assertThat(result.first).isNotEmpty() + assertThat(result.second).isEqualTo(Utils.LAST_24H_CONTENT_PROVIDER) + assertThat(result.third).isNotEmpty() + } + + @Test + fun getPermissionLastAccessSummaryTimestamp_nonSensorDataPermission_lastAccessSummaryTimestampIs7Days() { + val result = Utils.getPermissionLastAccessSummaryTimestamp( + System.currentTimeMillis() - 5 * 60 * 60 * 24 * 1000, context, STORAGE) + assertThat(result.first).isNotEmpty() + assertThat(result.second).isEqualTo(Utils.LAST_7D_CONTENT_PROVIDER) + assertThat(result.third).isNotEmpty() + } + + @Test + fun getOrGenerateSessionId_validSessionId() { val intent = Intent() intent.putExtra(EXTRA_SESSION_ID, VALID_SESSION_ID) val sessionId = Utils.getOrGenerateSessionId(intent) @@ -41,15 +219,15 @@ class UtilsTest { } @Test - fun assertGetOrGenerateSessionIdRandomSessionId() { + fun getOrGenerateSessionId_invalidSessionId() { val intent = Intent() val sessionId = Utils.getOrGenerateSessionId(intent) assertThat(sessionId).isNotEqualTo(INVALID_SESSION_ID) } @Test - fun assertGetGroupPermissionInfosValidGroup() { - val permissionInfos = Utils.getGroupPermissionInfos(GROUP_NAME, context) + fun getGroupPermissionInfos_validGroupName_returnsGroupPermissions() { + val permissionInfos = Utils.getGroupPermissionInfos(CONTACTS, context) assertThat(permissionInfos).isNotNull() val permissions = mutableListOf<String>() for (permissionInfo in permissionInfos!!) { @@ -59,25 +237,38 @@ class UtilsTest { } @Test - fun assertGetGroupPermissionInfosInValidGroup() { + fun getGroupPermissionInfos_inValidGroup_returnsNull() { assertThat(Utils.getGroupPermissionInfos(INVALID_GROUP_NAME, context)).isNull() } @Test - fun assertGetColorResIdValidId() { + fun getGroupPermissionInfos_undefinedGroup_returnsAllSystemPermissions() { + val permissionInfos = Utils.getGroupPermissionInfos(UNDEFINED, context) + assertThat(permissionInfos).isNotNull() + } + + @Test + fun getGroupPermissionInfo_permissionName_returnsSamePermission() { + val permissionInfos = Utils.getGroupPermissionInfos(READ_CONTACTS, context) + assertThat(permissionInfos).isNotNull() + assertThat(permissionInfos!!.size).isEqualTo(1) + assertThat(permissionInfos[0].name).isEqualTo(READ_CONTACTS) + } + + @Test + fun getColorResId_validId_returnsNonZero() { assertThat(Utils.getColorResId(context, android.R.attr.colorPrimary)) .isNotEqualTo(Resources.ID_NULL) } @Test - fun assertGetColorResIdInValidId() { + fun getColorResId_inValidId_returnsZero() { assertThat(Utils.getColorResId(context, INVALID_ATTR_ID)).isEqualTo(Resources.ID_NULL) } companion object { private const val INVALID_ATTR_ID = 1000 private const val VALID_SESSION_ID = 10000L - private const val GROUP_NAME = CONTACTS private const val INVALID_GROUP_NAME = "invalid group name" } } diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/HealthConnectPrivacySourceTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/HealthConnectPrivacySourceTest.kt deleted file mode 100644 index c68f1cc67..000000000 --- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/privacysources/HealthConnectPrivacySourceTest.kt +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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.tests.mocking.privacysources - -import android.app.PendingIntent -import android.app.PendingIntent.FLAG_IMMUTABLE -import android.app.PendingIntent.FLAG_UPDATE_CURRENT -import android.content.Context -import android.content.ContextWrapper -import android.content.Intent -import android.os.Build -import android.provider.DeviceConfig -import android.provider.DeviceConfig.NAMESPACE_PRIVACY -import android.safetycenter.SafetyCenterManager -import android.safetycenter.SafetyCenterManager.ACTION_REFRESH_SAFETY_SOURCES -import android.safetycenter.SafetyCenterManager.EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID -import android.safetycenter.SafetyEvent -import android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED -import android.safetycenter.SafetySourceData -import android.safetycenter.SafetySourceStatus -import androidx.test.core.app.ApplicationProvider -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SdkSuppress -import com.android.dx.mockito.inline.extended.ExtendedMockito -import com.android.permissioncontroller.PermissionControllerApplication -import com.android.permissioncontroller.permission.utils.Utils -import com.android.permissioncontroller.privacysources.SafetyCenterReceiver.RefreshEvent.EVENT_REFRESH_REQUESTED -import com.android.permissioncontroller.privacysources.v34.HealthConnectPrivacySource -import com.android.permissioncontroller.privacysources.v34.HealthConnectPrivacySource.Companion.HEALTH_CONNECT_SOURCE_ID -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchers.anyBoolean -import org.mockito.ArgumentMatchers.eq -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.Mockito.verifyZeroInteractions -import org.mockito.Mockito.`when` as whenever -import org.mockito.MockitoAnnotations -import org.mockito.MockitoSession -import org.mockito.quality.Strictness - -/** Tests for [HealthConnectPrivacySource]. */ -@RunWith(AndroidJUnit4::class) -@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake") -class HealthConnectPrivacySourceTest { - - private lateinit var mockitoSession: MockitoSession - private lateinit var healthConnectPrivacySource: HealthConnectPrivacySource - private lateinit var context: Context - @Mock lateinit var mockSafetyCenterManager: SafetyCenterManager - - @Before - fun setup() { - context = ApplicationProvider.getApplicationContext() - MockitoAnnotations.initMocks(this) - mockitoSession = - ExtendedMockito.mockitoSession() - .mockStatic(DeviceConfig::class.java) - .mockStatic(PermissionControllerApplication::class.java) - .mockStatic(Utils::class.java) - .strictness(Strictness.LENIENT) - .startMocking() - whenever( - Utils.getSystemServiceSafe( - any(ContextWrapper::class.java), - eq(SafetyCenterManager::class.java) - ) - ) - .thenReturn(mockSafetyCenterManager) - - healthConnectPrivacySource = HealthConnectPrivacySource() - } - - @After - fun cleanup() { - mockitoSession.finishMocking() - } - - @Test - fun safetyCenterEnabledChanged_enabled_doesNothing() { - healthConnectPrivacySource.safetyCenterEnabledChanged(context, true) - - verifyZeroInteractions(mockSafetyCenterManager) - } - - @Test - fun safetyCenterEnabledChanged_disabled_doesNothing() { - healthConnectPrivacySource.safetyCenterEnabledChanged(context, false) - - verifyZeroInteractions(mockSafetyCenterManager) - } - - @Test - fun rescanAndPushSafetyCenterData_healthPermissionUIEnabled_setsDataWithStatus() { - val refreshIntent = - Intent(ACTION_REFRESH_SAFETY_SOURCES) - .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_ID) - - healthConnectPrivacySource.rescanAndPushSafetyCenterData( - context, - refreshIntent, - EVENT_REFRESH_REQUESTED - ) - - val expectedSafetySourceData = - if (Utils.isHealthPermissionUiEnabled()) { - SafetySourceData.Builder() - .setStatus( - SafetySourceStatus.Builder( - HEALTH_CONNECT_TITLE, - HEALTH_CONNECT_SUMMARY, - SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED - ) - .setPendingIntent( - PendingIntent.getActivity( - context, - /* requestCode= */ 0, - Intent(HEALTH_CONNECT_INTENT_ACTION), - FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE - ) - ) - .build() - ) - .build() - } else { - null - } - - val expectedSafetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId(REFRESH_ID) - .build() - verify(mockSafetyCenterManager) - .setSafetySourceData( - HEALTH_CONNECT_SOURCE_ID, - expectedSafetySourceData, - expectedSafetyEvent - ) - } - - @Test - fun rescanAndPushSafetyCenterData_healthPermissionUIDisabled_setsNullData() { - setHealthPermissionUIEnabled(false) - val refreshIntent = - Intent(ACTION_REFRESH_SAFETY_SOURCES) - .putExtra(EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID, REFRESH_ID) - - healthConnectPrivacySource.rescanAndPushSafetyCenterData( - context, - refreshIntent, - EVENT_REFRESH_REQUESTED - ) - - val expectedSafetyEvent = - SafetyEvent.Builder(SAFETY_EVENT_TYPE_REFRESH_REQUESTED) - .setRefreshBroadcastId(REFRESH_ID) - .build() - verify(mockSafetyCenterManager) - .setSafetySourceData(HEALTH_CONNECT_SOURCE_ID, null, expectedSafetyEvent) - } - - /** Companion object for [HealthConnectPrivacySourceTest]. */ - companion object { - // Real context, used in order to avoid mocking resources. - const val HEALTH_CONNECT_TITLE: String = "Health Connect" - const val HEALTH_CONNECT_SUMMARY: String = "Manage app access to health data" - const val REFRESH_ID: String = "refresh_id" - const val HEALTH_CONNECT_INTENT_ACTION = - "android.health.connect.action.HEALTH_HOME_SETTINGS" - - /** Sets the value for the Health Permission feature [DeviceConfig] property. */ - private fun setHealthPermissionUIEnabled(enabled: Boolean) { - whenever( - DeviceConfig.getBoolean( - eq(NAMESPACE_PRIVACY), - eq("health_permission_ui_enabled"), - anyBoolean() - ) - ) - .thenReturn(enabled) - } - } -} diff --git a/SafetyCenter/Resources/res/raw-v34/safety_center_config.xml b/SafetyCenter/Resources/res/raw-v34/safety_center_config.xml index 5fbf6e9f2..1c3330c63 100644 --- a/SafetyCenter/Resources/res/raw-v34/safety_center_config.xml +++ b/SafetyCenter/Resources/res/raw-v34/safety_center_config.xml @@ -67,9 +67,9 @@ <dynamic-safety-source id="AndroidHealthConnect" profile="primary_profile_only" - packageName="com.android.permissioncontroller" + packageName="com.android.healthconnect.controller" initialDisplayState="hidden" - refreshOnPageOpenAllowed="true" + refreshOnPageOpenAllowed="false" title="@com.android.safetycenter.resources:string/health_connect_title" searchTerms="@com.android.safetycenter.resources:string/health_connect_search_terms"/> <dynamic-safety-source diff --git a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java index 5d03fa299..5d2d1ca88 100644 --- a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java +++ b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java @@ -597,7 +597,7 @@ public final class SafetyCenterDataFactory { } PendingIntent entryPendingIntent = safetySourceStatus.getPendingIntent(); boolean enabled = safetySourceStatus.isEnabled() && !inQuietMode; - if (entryPendingIntent == null) { + if (entryPendingIntent == null || inQuietMode) { entryPendingIntent = mPendingIntentFactory.getPendingIntent( safetySource.getId(), @@ -809,7 +809,15 @@ public final class SafetyCenterDataFactory { mSafetyCenterDataManager.getSafetySourceDataInternal(key)); boolean inQuietMode = isUserManaged && !isManagedUserRunning; if (safetySourceStatus != null) { - PendingIntent pendingIntent = safetySourceStatus.getPendingIntent(); + PendingIntent pendingIntent = + inQuietMode + ? mPendingIntentFactory.getPendingIntent( + safetySource.getId(), + safetySource.getIntentAction(), + safetySource.getPackageName(), + userId, + true) + : safetySourceStatus.getPendingIntent(); if (pendingIntent == null) { // TODO(b/222838784): Decide strategy for static entries when the intent is // null. diff --git a/service/java/com/android/safetycenter/SafetyCenterFlags.java b/service/java/com/android/safetycenter/SafetyCenterFlags.java index 39c9e1be8..1a9949523 100644 --- a/service/java/com/android/safetycenter/SafetyCenterFlags.java +++ b/service/java/com/android/safetycenter/SafetyCenterFlags.java @@ -131,6 +131,9 @@ public final class SafetyCenterFlags { private static volatile String sIssueCategoryAllowlistDefault = ""; + private static volatile String sRefreshOnPageOpenSourcesDefault = + "AndroidBiometrics,AndroidLockScreen"; + static void init(SafetyCenterResourcesContext resourceContext) { String untrackedSourcesDefault = resourceContext.getOptionalStringByName("config_defaultUntrackedSources"); @@ -147,6 +150,11 @@ public final class SafetyCenterFlags { if (issueCategoryAllowlistDefault != null) { sIssueCategoryAllowlistDefault = issueCategoryAllowlistDefault; } + String refreshOnPageOpenSourcesDefault = + resourceContext.getOptionalStringByName("config_defaultRefreshOnPageOpenSources"); + if (refreshOnPageOpenSourcesDefault != null) { + sRefreshOnPageOpenSourcesDefault = refreshOnPageOpenSourcesDefault; + } } private static final Duration TEMP_HIDDEN_ISSUE_RESURFACE_DELAY_DEFAULT_DURATION = @@ -468,7 +476,8 @@ public final class SafetyCenterFlags { * refreshOnPageOpenAllowed is false (the default) in the XML config. */ static ArraySet<String> getOverrideRefreshOnPageOpenSourceIds() { - return getCommaSeparatedStrings(PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES); + return getCommaSeparatedStrings( + PROPERTY_OVERRIDE_REFRESH_ON_PAGE_OPEN_SOURCES, sRefreshOnPageOpenSourcesDefault); } private static Duration getDuration(String property, Duration defaultValue) { |