summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/data/LauncherPackagesLiveData.kt19
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java6
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt112
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsPreference.kt49
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md4
-rw-r--r--framework-s/api/system-current.txt6
-rw-r--r--framework-s/java/android/safetycenter/SafetyCenterIssue.java54
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterListeners.java40
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterService.java8
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt36
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt91
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt8
12 files changed, 345 insertions, 88 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/LauncherPackagesLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/LauncherPackagesLiveData.kt
index da0f26000..f0d811821 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/LauncherPackagesLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/LauncherPackagesLiveData.kt
@@ -19,6 +19,7 @@ package com.android.permissioncontroller.permission.data
import android.content.Intent
import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE
import android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+import android.content.pm.PackageManager.FEATURE_LEANBACK
import com.android.permissioncontroller.PermissionControllerApplication
import kotlinx.coroutines.Job
@@ -31,14 +32,26 @@ object LauncherPackagesLiveData : SmartAsyncMediatorLiveData<Set<String>>(),
private val LAUNCHER_INTENT = Intent(Intent.ACTION_MAIN, null)
.addCategory(Intent.CATEGORY_LAUNCHER)
+ // On ATV some apps may have a leanback launcher icon but no regular launcher icon
+ private val LEANBACK_LAUNCHER_INTENT = Intent(Intent.ACTION_MAIN, null)
+ .addCategory(Intent.CATEGORY_LEANBACK_LAUNCHER)
+
override suspend fun loadDataAndPostValue(job: Job) {
val launcherPkgs = mutableSetOf<String>()
+
+ loadPkgsFromIntent(launcherPkgs, LAUNCHER_INTENT)
+ if (PermissionControllerApplication.get().packageManager
+ .hasSystemFeature(FEATURE_LEANBACK)) {
+ loadPkgsFromIntent(launcherPkgs, LEANBACK_LAUNCHER_INTENT)
+ }
+ postValue(launcherPkgs)
+ }
+
+ private fun loadPkgsFromIntent(launcherPkgs: MutableSet<String>, intent: Intent) {
for (info in PermissionControllerApplication.get().packageManager.queryIntentActivities(
- LAUNCHER_INTENT, MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)) {
+ intent, MATCH_DIRECT_BOOT_AWARE or MATCH_DIRECT_BOOT_UNAWARE)) {
launcherPkgs.add(info.activityInfo.packageName)
}
-
- postValue(launcherPkgs)
}
override fun onPackageUpdate(packageName: String) {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
index ecf9c00ea..d83714422 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/ManagePermissionsActivity.java
@@ -66,6 +66,7 @@ import com.android.permissioncontroller.permission.ui.handheld.PermissionAppsFra
import com.android.permissioncontroller.permission.ui.handheld.dashboard.PermissionDetailsWrapperFragment;
import com.android.permissioncontroller.permission.ui.handheld.dashboard.PermissionUsageV2WrapperFragment;
import com.android.permissioncontroller.permission.ui.legacy.AppPermissionActivity;
+import com.android.permissioncontroller.permission.ui.television.TvUnusedAppsFragment;
import com.android.permissioncontroller.permission.ui.wear.AppPermissionsFragmentWear;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.Utils;
@@ -375,7 +376,10 @@ public final class ManagePermissionsActivity extends SettingsActivity {
if (DeviceUtils.isAuto(this)) {
androidXFragment = AutoUnusedAppsFragment.newInstance();
androidXFragment.setArguments(UnusedAppsFragment.createArgs(sessionId));
- } else if (DeviceUtils.isWear(this) || DeviceUtils.isTelevision(this)) {
+ } else if (DeviceUtils.isTelevision(this)) {
+ androidXFragment = TvUnusedAppsFragment.newInstance();
+ androidXFragment.setArguments(UnusedAppsFragment.createArgs(sessionId));
+ } else if (DeviceUtils.isWear(this)) {
androidXFragment = HandheldUnusedAppsWrapperFragment.newInstance();
androidXFragment.setArguments(UnusedAppsFragment.createArgs(sessionId));
} else {
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt
new file mode 100644
index 000000000..6caaa7420
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsFragment.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.permissioncontroller.permission.ui.television
+
+import android.app.Application
+import android.os.Bundle
+import android.os.UserHandle
+import androidx.preference.Preference
+import androidx.preference.PreferenceCategory
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.hibernation.isHibernationEnabled
+import com.android.permissioncontroller.permission.ui.UnusedAppsFragment
+import com.android.permissioncontroller.permission.ui.UnusedAppsFragment.Companion.INFO_MSG_CATEGORY
+
+/**
+ * TV wrapper, with customizations, around [UnusedAppsFragment].
+ */
+class TvUnusedAppsFragment : SettingsWithHeader(),
+ UnusedAppsFragment.Parent<TvUnusedAppsPreference> {
+
+ companion object {
+ private const val UNUSED_PREFERENCE_KEY = "unused_pref_row_key"
+
+ /** Create a new instance of this fragment. */
+ @JvmStatic
+ fun newInstance(): TvUnusedAppsFragment {
+ return TvUnusedAppsFragment()
+ }
+ }
+
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ // Preferences will be added via shared logic in [UnusedAppsFragment].
+ }
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ if (savedInstanceState == null) {
+ val fragment:
+ UnusedAppsFragment<TvUnusedAppsFragment, TvUnusedAppsPreference> =
+ UnusedAppsFragment.newInstance()
+ fragment.arguments = arguments
+ // child fragment does not have its own UI - it will add to the preferences of this
+ // parent fragment
+ childFragmentManager.beginTransaction()
+ .add(fragment, null)
+ .commit()
+ }
+ }
+
+ override fun createFooterPreference(): Preference {
+ val preference = Preference(context)
+ if (isHibernationEnabled()) {
+ preference.summary = getString(R.string.unused_apps_page_summary)
+ } else {
+ preference.summary =
+ getString(R.string.auto_revoked_apps_page_summary)
+ }
+ preference.setIcon(R.drawable.ic_info_outline)
+ preference.isSelectable = false
+ return preference
+ }
+
+ override fun setLoadingState(loading: Boolean, animate: Boolean) {
+ setLoading(loading, animate)
+ }
+
+ override fun createUnusedAppPref(
+ app: Application,
+ packageName: String,
+ user: UserHandle
+ ): TvUnusedAppsPreference {
+ return TvUnusedAppsPreference(app, packageName, user, requireContext())
+ }
+
+ override fun setTitle(title: CharSequence) {
+ setHeader(null, null, null, title)
+ }
+
+ override fun setEmptyState(empty: Boolean) {
+ val infoMsgCategory =
+ preferenceScreen.findPreference<PreferenceCategory>(INFO_MSG_CATEGORY)!!
+ val noUnusedAppsPreference: Preference? =
+ infoMsgCategory.findPreference<Preference>(UNUSED_PREFERENCE_KEY)
+ if (empty && noUnusedAppsPreference == null) {
+ infoMsgCategory.addPreference(createNoUnusedAppsPreference())
+ } else if (noUnusedAppsPreference != null) {
+ noUnusedAppsPreference.setVisible(empty)
+ }
+ }
+
+ private fun createNoUnusedAppsPreference(): Preference {
+ val preference = Preference(context)
+ preference.title = getString(R.string.zero_unused_apps)
+ preference.key = UNUSED_PREFERENCE_KEY
+ preference.isSelectable = false
+ preference.order = 0
+ return preference
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsPreference.kt
new file mode 100644
index 000000000..23f4a4589
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/television/TvUnusedAppsPreference.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.permissioncontroller.permission.ui.television
+
+import androidx.preference.Preference
+import android.app.Application
+import android.content.Context
+import android.os.UserHandle
+import com.android.permissioncontroller.permission.ui.RemovablePref
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+
+/**
+ * A TV-styled preference which represents an app that has been auto revoked. Has the app icon and
+ * label, as well as a button to open the app.
+ *
+ * @param app The current application
+ * @param packageName The name of the package whose icon this preference will retrieve
+ * @param user The user whose package icon will be retrieved
+ * @param context The current context
+ */
+class TvUnusedAppsPreference(
+ app: Application,
+ packageName: String,
+ user: UserHandle,
+ context: Context
+) : Preference(context), RemovablePref {
+
+ init {
+ icon = KotlinUtils.getBadgedPackageIcon(app, packageName, user)
+ }
+
+ override fun setRemoveClickRunnable(runnable: Runnable) {
+ // TV Settings don't have secondary icons and actions
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md b/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md
index 975840aaa..f632332f3 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md
+++ b/PermissionController/src/com/android/permissioncontroller/role/RolePermissionProtection.md
@@ -93,8 +93,8 @@ overlay.
Since role is modularized, we also need to make this config resource a system API for access by
role.
-Edit `frameworks/base/core/res/res/values/public.xml` to expose the new config resource as a system
-API:
+Edit `frameworks/base/core/res/res/values/public-staging.xml` to expose the new config resource as
+a system API:
```xml
<staging-public-group type="string" first-id="0xXXXXXXXX">
diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt
index 42091733e..ecf36e9c0 100644
--- a/framework-s/api/system-current.txt
+++ b/framework-s/api/system-current.txt
@@ -170,7 +170,7 @@ package android.safetycenter {
method @NonNull public android.app.PendingIntent getPendingIntent();
method @Nullable public CharSequence getSuccessMessage();
method public boolean isInFlight();
- method public boolean isResolving();
+ method public boolean willResolve();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.safetycenter.SafetyCenterIssue.Action> CREATOR;
}
@@ -178,11 +178,11 @@ package android.safetycenter {
public static final class SafetyCenterIssue.Action.Builder {
ctor public SafetyCenterIssue.Action.Builder(@NonNull String);
method @NonNull public android.safetycenter.SafetyCenterIssue.Action build();
- method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setInFlight(boolean);
+ method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setIsInFlight(boolean);
method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setLabel(@NonNull CharSequence);
method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setPendingIntent(@NonNull android.app.PendingIntent);
- method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setResolving(boolean);
method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setSuccessMessage(@Nullable CharSequence);
+ method @NonNull public android.safetycenter.SafetyCenterIssue.Action.Builder setWillResolve(boolean);
}
public static final class SafetyCenterIssue.Builder {
diff --git a/framework-s/java/android/safetycenter/SafetyCenterIssue.java b/framework-s/java/android/safetycenter/SafetyCenterIssue.java
index 9f5b83c55..3371bc852 100644
--- a/framework-s/java/android/safetycenter/SafetyCenterIssue.java
+++ b/framework-s/java/android/safetycenter/SafetyCenterIssue.java
@@ -23,6 +23,7 @@ import static java.util.Objects.requireNonNull;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.PendingIntent;
import android.os.Parcel;
@@ -370,7 +371,7 @@ public final class SafetyCenterIssue implements Parcelable {
private final CharSequence mLabel;
@NonNull
private final PendingIntent mPendingIntent;
- private final boolean mResolving;
+ private final boolean mWillResolve;
private final boolean mInFlight;
@Nullable
private final CharSequence mSuccessMessage;
@@ -379,13 +380,13 @@ public final class SafetyCenterIssue implements Parcelable {
@NonNull String id,
@NonNull CharSequence label,
@NonNull PendingIntent pendingIntent,
- boolean resolving,
+ boolean willResolve,
boolean inFlight,
@Nullable CharSequence successMessage) {
mId = requireNonNull(id);
mLabel = requireNonNull(label);
mPendingIntent = requireNonNull(pendingIntent);
- mResolving = resolving;
+ mWillResolve = willResolve;
mInFlight = inFlight;
mSuccessMessage = successMessage;
}
@@ -410,14 +411,18 @@ public final class SafetyCenterIssue implements Parcelable {
/**
* Returns whether invoking this action will fix or address the issue sufficiently for it
- * to be considered resolved i.e. the issue will no longer need to be conveyed to the user
- * in the UI.
+ * to be considered resolved (i.e. the issue will no longer need to be conveyed to the user
+ * in the UI).
*/
- public boolean isResolving() {
- return mResolving;
+ public boolean willResolve() {
+ return mWillResolve;
}
- /** Returns whether or not this action is currently being executed. */
+ /**
+ * Returns whether or not this action is currently being executed (i.e. the user clicked
+ * on a button that triggered this action, and now the Safety Center is waiting for the
+ * action's result).
+ */
public boolean isInFlight() {
return mInFlight;
}
@@ -439,7 +444,7 @@ public final class SafetyCenterIssue implements Parcelable {
return Objects.equals(mId, action.mId)
&& TextUtils.equals(mLabel, action.mLabel)
&& Objects.equals(mPendingIntent, action.mPendingIntent)
- && mResolving == action.mResolving
+ && mWillResolve == action.mWillResolve
&& mInFlight == action.mInFlight
&& TextUtils.equals(mSuccessMessage, action.mSuccessMessage);
}
@@ -447,7 +452,7 @@ public final class SafetyCenterIssue implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(
- mId, mLabel, mSuccessMessage, mResolving, mInFlight, mPendingIntent);
+ mId, mLabel, mSuccessMessage, mWillResolve, mInFlight, mPendingIntent);
}
@Override
@@ -456,7 +461,7 @@ public final class SafetyCenterIssue implements Parcelable {
+ "mId=" + mId
+ ", mLabel=" + mLabel
+ ", mPendingIntent=" + mPendingIntent
- + ", mResolving=" + mResolving
+ + ", mWillResolve=" + mWillResolve
+ ", mInFlight=" + mInFlight
+ ", mSuccessMessage=" + mSuccessMessage
+ '}';
@@ -472,7 +477,7 @@ public final class SafetyCenterIssue implements Parcelable {
dest.writeString(mId);
TextUtils.writeToParcel(mLabel, dest, flags);
dest.writeParcelable(mPendingIntent, flags);
- dest.writeBoolean(mResolving);
+ dest.writeBoolean(mWillResolve);
dest.writeBoolean(mInFlight);
TextUtils.writeToParcel(mSuccessMessage, dest, flags);
}
@@ -486,8 +491,8 @@ public final class SafetyCenterIssue implements Parcelable {
.setPendingIntent(
in.readParcelable(
PendingIntent.class.getClassLoader(), PendingIntent.class))
- .setResolving(in.readBoolean())
- .setInFlight(in.readBoolean())
+ .setWillResolve(in.readBoolean())
+ .setIsInFlight(in.readBoolean())
.setSuccessMessage(TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in))
.build();
}
@@ -503,7 +508,7 @@ public final class SafetyCenterIssue implements Parcelable {
private String mId;
private CharSequence mLabel;
private PendingIntent mPendingIntent;
- private boolean mResolving;
+ private boolean mWillResolve;
private boolean mInFlight;
private CharSequence mSuccessMessage;
@@ -529,23 +534,28 @@ public final class SafetyCenterIssue implements Parcelable {
}
/**
- * Sets whether or not this action is resolving. Defaults to false.
+ * Sets whether or not this action will resolve the issue when executed. Defaults to
+ * false.
*
- * @see #isResolving()
+ * @see #willResolve()
*/
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder setResolving(boolean resolving) {
- mResolving = resolving;
+ public Builder setWillResolve(boolean willResolve) {
+ mWillResolve = willResolve;
return this;
}
/**
- * Sets whether or not this action is in flight. Defaults to false.
+ * Sets a boolean that indicates whether or not this action is currently being executed
+ * (i.e. the user clicked on a button that triggered this action, and now the Safety
+ * Center is waiting for the action's result). Defaults to false.
*
* @see #isInFlight()
*/
+ @SuppressLint("MissingGetterMatchingBuilder")
@NonNull
- public Builder setInFlight(boolean inFlight) {
+ public Builder setIsInFlight(boolean inFlight) {
mInFlight = inFlight;
return this;
}
@@ -564,7 +574,7 @@ public final class SafetyCenterIssue implements Parcelable {
@NonNull
public Action build() {
return new Action(
- mId, mLabel, mPendingIntent, mResolving, mInFlight, mSuccessMessage);
+ mId, mLabel, mPendingIntent, mWillResolve, mInFlight, mSuccessMessage);
}
}
}
diff --git a/service/java/com/android/safetycenter/SafetyCenterListeners.java b/service/java/com/android/safetycenter/SafetyCenterListeners.java
index 184d23429..f1bb012e3 100644
--- a/service/java/com/android/safetycenter/SafetyCenterListeners.java
+++ b/service/java/com/android/safetycenter/SafetyCenterListeners.java
@@ -21,11 +21,11 @@ import static android.os.Build.VERSION_CODES.TIRAMISU;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
-import android.os.Binder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.safetycenter.IOnSafetyCenterDataChangedListener;
import android.safetycenter.SafetyCenterData;
+import android.safetycenter.SafetyCenterErrorDetails;
import android.util.Log;
import android.util.SparseArray;
@@ -56,13 +56,10 @@ final class SafetyCenterListeners {
static void deliverUpdate(
@NonNull IOnSafetyCenterDataChangedListener listener,
@NonNull SafetyCenterData safetyCenterData) {
- final long identity = Binder.clearCallingIdentity();
try {
listener.onSafetyCenterDataChanged(safetyCenterData);
} catch (RemoteException e) {
- Log.e(TAG, "Error delivering SafetyCenterData update to listener", e);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ Log.e(TAG, "Error delivering SafetyCenterData to listener", e);
}
}
@@ -85,6 +82,39 @@ final class SafetyCenterListeners {
listeners.finishBroadcast();
}
+ /**
+ * Delivers a {@link SafetyCenterErrorDetails} update to a single {@link
+ * IOnSafetyCenterDataChangedListener}.
+ */
+ private static void deliverError(
+ @NonNull IOnSafetyCenterDataChangedListener listener,
+ @NonNull SafetyCenterErrorDetails safetyCenterErrorDetails) {
+ try {
+ listener.onError(safetyCenterErrorDetails);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error delivering SafetyCenterErrorDetails to listener", e);
+ }
+ }
+
+ /**
+ * Delivers a {@link SafetyCenterErrorDetails} update to a {@link RemoteCallbackList} of {@link
+ * IOnSafetyCenterDataChangedListener}.
+ *
+ * <p>Registering or unregistering {@link IOnSafetyCenterDataChangedListener} on the underlying
+ * {@link RemoteCallbackList} on another thread while an update is happening is safe as this is
+ * handled by the {@link RemoteCallbackList} already (as well as listeners death).
+ */
+ static void deliverError(
+ @NonNull RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners,
+ @NonNull SafetyCenterErrorDetails safetyCenterErrorDetails) {
+ int i = listeners.beginBroadcast();
+ while (i > 0) {
+ i--;
+ deliverError(listeners.getBroadcastItem(i), safetyCenterErrorDetails);
+ }
+ listeners.finishBroadcast();
+ }
+
/** Adds a {@link IOnSafetyCenterDataChangedListener} for the given {@code userId}. */
void addListener(
@NonNull IOnSafetyCenterDataChangedListener listener,
diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java
index cea0ae404..6096925b8 100644
--- a/service/java/com/android/safetycenter/SafetyCenterService.java
+++ b/service/java/com/android/safetycenter/SafetyCenterService.java
@@ -39,6 +39,7 @@ import android.provider.DeviceConfig;
import android.safetycenter.IOnSafetyCenterDataChangedListener;
import android.safetycenter.ISafetyCenterManager;
import android.safetycenter.SafetyCenterData;
+import android.safetycenter.SafetyCenterErrorDetails;
import android.safetycenter.SafetyEvent;
import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceErrorDetails;
@@ -194,7 +195,14 @@ public final class SafetyCenterService extends SystemService {
if (!checkApiEnabled("reportSafetySourceError")) {
return;
}
+
// TODO(b/218379298): Add implementation
+ RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners;
+ synchronized (mApiLock) {
+ listeners = mSafetyCenterListeners.getListeners(userId);
+ }
+
+ SafetyCenterListeners.deliverError(listeners, new SafetyCenterErrorDetails("Error"));
}
@Override
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
index a8428049c..819124611 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
@@ -48,15 +48,15 @@ class SafetyCenterIssueTest {
val action1 = SafetyCenterIssue.Action.Builder("action_id_1")
.setLabel("an action")
.setPendingIntent(pendingIntent1)
- .setResolving(true)
- .setInFlight(true)
+ .setWillResolve(true)
+ .setIsInFlight(true)
.setSuccessMessage("a success message")
.build()
val action2 = SafetyCenterIssue.Action.Builder("action_id_2")
.setLabel("another action")
.setPendingIntent(pendingIntent2)
- .setResolving(false)
- .setInFlight(false)
+ .setWillResolve(false)
+ .setIsInFlight(false)
.build()
val issue1 = SafetyCenterIssue.Builder("issue_id")
@@ -330,9 +330,9 @@ class SafetyCenterIssueTest {
}
@Test
- fun action_isResolving_returnsIsResolving() {
- assertThat(action1.isResolving).isTrue()
- assertThat(action2.isResolving).isFalse()
+ fun action_willResolve_returnsWillResolve() {
+ assertThat(action1.willResolve()).isTrue()
+ assertThat(action2.willResolve()).isFalse()
}
@Test
@@ -377,15 +377,15 @@ class SafetyCenterIssueTest {
val action = SafetyCenterIssue.Action.Builder("an_id")
.setLabel("a label")
.setPendingIntent(pendingIntent1)
- .setResolving(true)
- .setInFlight(true)
+ .setWillResolve(true)
+ .setIsInFlight(true)
.setSuccessMessage("a success message")
.build()
val equivalentAction = SafetyCenterIssue.Action.Builder("an_id")
.setLabel("a label")
.setPendingIntent(pendingIntent1)
- .setResolving(true)
- .setInFlight(true)
+ .setWillResolve(true)
+ .setIsInFlight(true)
.setSuccessMessage("a success message")
.build()
@@ -445,17 +445,17 @@ class SafetyCenterIssueTest {
}
@Test
- fun action_equals_toString_differentResovlingValues_areNotEqual() {
+ fun action_equals_toString_differentWillResolveValues_areNotEqual() {
val action = SafetyCenterIssue.Action.Builder("an_id")
.setLabel("a label")
.setPendingIntent(pendingIntent1)
- .setResolving(true)
+ .setWillResolve(true)
.setSuccessMessage("a success message")
.build()
val differentAction = SafetyCenterIssue.Action.Builder("an_id")
.setLabel("a label")
.setPendingIntent(pendingIntent1)
- .setResolving(false)
+ .setWillResolve(false)
.setSuccessMessage("a success message")
.build()
@@ -468,15 +468,15 @@ class SafetyCenterIssueTest {
val action = SafetyCenterIssue.Action.Builder("an_id")
.setLabel("a label")
.setPendingIntent(pendingIntent1)
- .setResolving(true)
- .setInFlight(true)
+ .setWillResolve(true)
+ .setIsInFlight(true)
.setSuccessMessage("a success message")
.build()
val differentAction = SafetyCenterIssue.Action.Builder("an_id")
.setLabel("a label")
.setPendingIntent(pendingIntent1)
- .setResolving(true)
- .setInFlight(false)
+ .setWillResolve(true)
+ .setIsInFlight(false)
.setSuccessMessage("a success message")
.build()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
index a574a7892..af785eecf 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
@@ -25,6 +25,7 @@ import android.content.Intent
import android.content.Intent.ACTION_SAFETY_CENTER
import android.os.Build.VERSION_CODES.TIRAMISU
import android.safetycenter.SafetyCenterData
+import android.safetycenter.SafetyCenterErrorDetails
import android.safetycenter.SafetyCenterManager
import android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener
import android.safetycenter.SafetyCenterManager.REFRESH_REASON_PAGE_OPEN
@@ -56,6 +57,7 @@ import android.safetycenter.testing.getSafetySourceDataWithPermission
import android.safetycenter.testing.isSafetyCenterEnabledWithPermission
import android.safetycenter.testing.refreshSafetySourcesWithPermission
import android.safetycenter.testing.removeOnSafetyCenterDataChangedListenerWithPermission
+import android.safetycenter.testing.reportSafetySourceErrorWithPermission
import android.safetycenter.testing.setSafetyCenterConfigOverrideWithPermission
import android.safetycenter.testing.setSafetySourceDataWithPermission
import androidx.test.core.app.ApplicationProvider.getApplicationContext
@@ -135,13 +137,28 @@ class SafetyCenterManagerTest {
.build()
)
.build()
- private val listenerChannel = Channel<SafetyCenterData>()
+ private val listener = object : OnSafetyCenterDataChangedListener {
+ private val dataChannel = Channel<SafetyCenterData>()
+ private val errorChannel = Channel<SafetyCenterErrorDetails>()
- // The lambda has to be wrapped to the right type because kotlin wraps lambdas in a new Java
- // functional interface implementation each time they are referenced/cast to a Java interface:
- // b/215569072.
- private val listener = OnSafetyCenterDataChangedListener {
- runBlockingWithTimeout { listenerChannel.send(it) }
+ override fun onSafetyCenterDataChanged(data: SafetyCenterData) {
+ runBlockingWithTimeout { dataChannel.send(data) }
+ }
+
+ override fun onError(errorDetails: SafetyCenterErrorDetails) {
+ runBlockingWithTimeout { errorChannel.send(errorDetails) }
+ }
+
+ fun receiveSafetyCenterData(timeout: Duration = TIMEOUT_LONG) =
+ runBlockingWithTimeout(timeout) { dataChannel.receive() }
+
+ fun receiveSafetyCenterErrorDetails(timeout: Duration = TIMEOUT_LONG) =
+ runBlockingWithTimeout(timeout) { errorChannel.receive() }
+
+ fun cancelChannels() {
+ dataChannel.cancel()
+ errorChannel.cancel()
+ }
}
@Before
@@ -160,8 +177,8 @@ class SafetyCenterManagerTest {
}
@After
- fun cancelChannelAfterTest() {
- listenerChannel.cancel()
+ fun cancelChannelsAfterTest() {
+ listener.cancelChannels()
}
@Test
@@ -284,6 +301,23 @@ class SafetyCenterManagerTest {
}
@Test
+ fun reportSafetySourceError_callsErrorListener() {
+ safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
+ directExecutor(),
+ listener
+ )
+
+ safetyCenterManager.reportSafetySourceErrorWithPermission(
+ CTS_SOURCE_ID,
+ SafetySourceErrorDetails(EVENT_SOURCE_STATE_CHANGED)
+ )
+ val safetyCenterErrorDetailsFromListener = listener.receiveSafetyCenterErrorDetails()
+
+ assertThat(safetyCenterErrorDetailsFromListener).isEqualTo(
+ SafetyCenterErrorDetails("Error"))
+ }
+
+ @Test
fun reportSafetySourceError_withoutPermission_throwsSecurityException() {
assertFailsWith(SecurityException::class) {
safetyCenterManager.reportSafetySourceError(
@@ -448,7 +482,7 @@ class SafetyCenterManagerTest {
directExecutor(),
listener
)
- val safetyCenterDataFromListener = receiveListenerUpdate()
+ val safetyCenterDataFromListener = listener.receiveSafetyCenterData()
// TODO(b/218830137): Assert on content.
assertThat(safetyCenterDataFromListener).isNotNull()
@@ -462,14 +496,14 @@ class SafetyCenterManagerTest {
listener
)
// Receive initial data.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
safetyCenterManager.setSafetySourceDataWithPermission(
CTS_SOURCE_ID,
safetySourceDataOk,
EVENT_SOURCE_STATE_CHANGED
)
- val safetyCenterDataFromListener = receiveListenerUpdate()
+ val safetyCenterDataFromListener = listener.receiveSafetyCenterData()
// TODO(b/218830137): Assert on content.
assertThat(safetyCenterDataFromListener).isNotNull()
@@ -483,21 +517,21 @@ class SafetyCenterManagerTest {
listener
)
// Receive initial data.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
safetyCenterManager.setSafetySourceDataWithPermission(
CTS_SOURCE_ID,
safetySourceDataOk,
EVENT_SOURCE_STATE_CHANGED
)
// Receive update from #setSafetySourceData call.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
safetyCenterManager.setSafetySourceDataWithPermission(
CTS_SOURCE_ID,
safetySourceDataCritical,
EVENT_SOURCE_STATE_CHANGED
)
- val safetyCenterDataFromListener = receiveListenerUpdate()
+ val safetyCenterDataFromListener = listener.receiveSafetyCenterData()
// TODO(b/218830137): Assert on content.
assertThat(safetyCenterDataFromListener).isNotNull()
@@ -511,21 +545,21 @@ class SafetyCenterManagerTest {
listener
)
// Receive initial data.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
safetyCenterManager.setSafetySourceDataWithPermission(
CTS_SOURCE_ID,
safetySourceDataOk,
EVENT_SOURCE_STATE_CHANGED
)
// Receive update from #setSafetySourceData call.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
safetyCenterManager.setSafetySourceDataWithPermission(
CTS_SOURCE_ID,
safetySourceData = null,
EVENT_SOURCE_STATE_CHANGED
)
- val safetyCenterDataFromListener = receiveListenerUpdate()
+ val safetyCenterDataFromListener = listener.receiveSafetyCenterData()
// TODO(b/218830137): Assert on content.
assertThat(safetyCenterDataFromListener).isNotNull()
@@ -539,7 +573,7 @@ class SafetyCenterManagerTest {
listener
)
// Receive initial data.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
safetyCenterManager.setSafetySourceDataWithPermission(
CTS_SOURCE_ID,
@@ -548,7 +582,7 @@ class SafetyCenterManagerTest {
)
assertFailsWith(TimeoutCancellationException::class) {
- receiveListenerUpdate(TIMEOUT_SHORT)
+ listener.receiveSafetyCenterData(TIMEOUT_SHORT)
}
}
@@ -560,14 +594,14 @@ class SafetyCenterManagerTest {
listener
)
// Receive initial data.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
safetyCenterManager.setSafetySourceDataWithPermission(
CTS_SOURCE_ID,
safetySourceDataOk,
EVENT_SOURCE_STATE_CHANGED
)
// Receive update from #setSafetySourceData call.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
safetyCenterManager.setSafetySourceDataWithPermission(
CTS_SOURCE_ID,
@@ -576,7 +610,7 @@ class SafetyCenterManagerTest {
)
assertFailsWith(TimeoutCancellationException::class) {
- receiveListenerUpdate(TIMEOUT_SHORT)
+ listener.receiveSafetyCenterData(TIMEOUT_SHORT)
}
}
@@ -591,7 +625,7 @@ class SafetyCenterManagerTest {
)
assertFailsWith(TimeoutCancellationException::class) {
- receiveListenerUpdate(TIMEOUT_SHORT)
+ listener.receiveSafetyCenterData(TIMEOUT_SHORT)
}
}
@@ -608,7 +642,7 @@ class SafetyCenterManagerTest {
object : OnSafetyCenterDataChangedListener {
override fun onSafetyCenterDataChanged(safetyCenterData: SafetyCenterData) {
safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(this)
- runBlockingWithTimeout { listenerChannel.send(safetyCenterData) }
+ listener.onSafetyCenterDataChanged(safetyCenterData)
}
}
safetyCenterManager.addOnSafetyCenterDataChangedListenerWithPermission(
@@ -619,7 +653,7 @@ class SafetyCenterManagerTest {
// Check that we don't deadlock when using a one-shot listener: this is because adding the
// listener could call the listener while holding a lock on the binder thread-pool; causing
// a deadlock when attempting to call the `SafetyCenterManager` from that listener.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
}
@Test
@@ -630,7 +664,7 @@ class SafetyCenterManagerTest {
listener
)
// Receive initial data.
- receiveListenerUpdate()
+ listener.receiveSafetyCenterData()
safetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(listener)
safetyCenterManager.setSafetySourceDataWithPermission(
@@ -640,7 +674,7 @@ class SafetyCenterManagerTest {
)
assertFailsWith(TimeoutCancellationException::class) {
- receiveListenerUpdate(TIMEOUT_SHORT)
+ listener.receiveSafetyCenterData(TIMEOUT_SHORT)
}
}
@@ -703,9 +737,6 @@ class SafetyCenterManagerTest {
)
}
- private fun receiveListenerUpdate(timeout: Duration = TIMEOUT_LONG): SafetyCenterData =
- runBlockingWithTimeout(timeout) { listenerChannel.receive() }
-
private fun <T> runBlockingWithTimeout(
timeout: Duration = TIMEOUT_LONG,
block: suspend () -> T
diff --git a/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt b/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt
index 153390492..b874b8219 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/testing/SafetyCenterApisWithShellPermissions.kt
@@ -57,7 +57,7 @@ fun SafetyCenterManager.getSafetySourceDataWithPermission(id: String) =
callWithShellPermissionIdentity({ getSafetySourceData(id) }, SEND_SAFETY_CENTER_UPDATE)
/**
- * Calls [SafetyCenterManager.reportSafetySourceError] adopting Shell's [MANAGE_SAFETY_CENTER]
+ * Calls [SafetyCenterManager.reportSafetySourceError] adopting Shell's [SEND_SAFETY_CENTER_UPDATE]
* permission.
*/
fun SafetyCenterManager.reportSafetySourceErrorWithPermission(
@@ -66,7 +66,7 @@ fun SafetyCenterManager.reportSafetySourceErrorWithPermission(
) =
callWithShellPermissionIdentity(
{ reportSafetySourceError(safetySourceId, safetySourceErrorDetails) },
- MANAGE_SAFETY_CENTER
+ SEND_SAFETY_CENTER_UPDATE
)
/**
@@ -112,7 +112,7 @@ fun SafetyCenterManager.removeOnSafetyCenterDataChangedListenerWithPermission(
* Calls [SafetyCenterManager.dismissSafetyCenterIssue] adopting Shell's [MANAGE_SAFETY_CENTER]
* permission.
*/
-fun SafetyCenterManager.dismissSafetyIssueWithPermission(safetyCenterIssueId: String) =
+fun SafetyCenterManager.dismissSafetyCenterIssueWithPermission(safetyCenterIssueId: String) =
callWithShellPermissionIdentity({ dismissSafetyCenterIssue(safetyCenterIssueId) },
MANAGE_SAFETY_CENTER)
@@ -120,7 +120,7 @@ fun SafetyCenterManager.dismissSafetyIssueWithPermission(safetyCenterIssueId: St
* Calls [SafetyCenterManager.executeSafetyCenterIssueAction] adopting Shell's
* [MANAGE_SAFETY_CENTER] permission.
*/
-fun SafetyCenterManager.executeSafetyCenterActionWithPermission(
+fun SafetyCenterManager.executeSafetyCenterIssueActionWithPermission(
safetyCenterIssueId: String,
safetyCenterIssueActionId: String
) =