722 lines
30 KiB
Java
722 lines
30 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2021 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 android.safetycenter;
|
||
|
|
||
|
import static android.Manifest.permission.MANAGE_SAFETY_CENTER;
|
||
|
import static android.Manifest.permission.READ_SAFETY_CENTER_STATUS;
|
||
|
import static android.Manifest.permission.SEND_SAFETY_CENTER_UPDATE;
|
||
|
import static android.annotation.SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION;
|
||
|
import static android.os.Build.VERSION_CODES.TIRAMISU;
|
||
|
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
|
||
|
|
||
|
import static java.util.Objects.requireNonNull;
|
||
|
|
||
|
import android.annotation.CallbackExecutor;
|
||
|
import android.annotation.IntDef;
|
||
|
import android.annotation.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.annotation.RequiresPermission;
|
||
|
import android.annotation.SdkConstant;
|
||
|
import android.annotation.SystemApi;
|
||
|
import android.annotation.SystemService;
|
||
|
import android.annotation.TargetApi;
|
||
|
import android.content.Context;
|
||
|
import android.content.Intent;
|
||
|
import android.os.Binder;
|
||
|
import android.os.RemoteException;
|
||
|
import android.safetycenter.config.SafetyCenterConfig;
|
||
|
import android.util.ArrayMap;
|
||
|
|
||
|
import androidx.annotation.RequiresApi;
|
||
|
|
||
|
import com.android.internal.annotations.GuardedBy;
|
||
|
import com.android.modules.utils.build.SdkLevel;
|
||
|
|
||
|
import java.lang.annotation.Retention;
|
||
|
import java.lang.annotation.RetentionPolicy;
|
||
|
import java.util.List;
|
||
|
import java.util.Map;
|
||
|
import java.util.concurrent.Executor;
|
||
|
|
||
|
/**
|
||
|
* Interface for communicating with the Safety Center, which consolidates UI for security and
|
||
|
* privacy features on the device.
|
||
|
*
|
||
|
* <p>These APIs are intended to be used by the following clients:
|
||
|
*
|
||
|
* <ul>
|
||
|
* <li>Safety sources represented in Safety Center UI
|
||
|
* <li>Dependents on the state of Safety Center UI
|
||
|
* <li>Managers of Safety Center UI
|
||
|
* </ul>
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@SystemService(Context.SAFETY_CENTER_SERVICE)
|
||
|
@SystemApi
|
||
|
@RequiresApi(TIRAMISU)
|
||
|
public final class SafetyCenterManager {
|
||
|
|
||
|
/**
|
||
|
* Broadcast Action: A broadcast sent by the system to indicate that the value returned by
|
||
|
* {@link SafetyCenterManager#isSafetyCenterEnabled()} has changed.
|
||
|
*
|
||
|
* <p>This broadcast will inform receivers about changes to {@link
|
||
|
* SafetyCenterManager#isSafetyCenterEnabled()}, should they want to check the new value and
|
||
|
* enable/disable components accordingly.
|
||
|
*
|
||
|
* <p>This broadcast is sent explicitly to safety sources by targeting intents to a specified
|
||
|
* set of packages in the {@link SafetyCenterConfig}. The receiving components must hold the
|
||
|
* {@link android.Manifest.permission#SEND_SAFETY_CENTER_UPDATE} permission, and can use a
|
||
|
* manifest-registered receiver to be woken up by Safety Center.
|
||
|
*
|
||
|
* <p>This broadcast is also sent implicitly system-wide. The receiving components must hold the
|
||
|
* {@link android.Manifest.permission#READ_SAFETY_CENTER_STATUS} permission.
|
||
|
*
|
||
|
* <p>This broadcast is not sent out if the device does not support Safety Center.
|
||
|
*
|
||
|
* <p class="note">This is a protected intent that can only be sent by the system.
|
||
|
*/
|
||
|
@SdkConstant(BROADCAST_INTENT_ACTION)
|
||
|
public static final String ACTION_SAFETY_CENTER_ENABLED_CHANGED =
|
||
|
"android.safetycenter.action.SAFETY_CENTER_ENABLED_CHANGED";
|
||
|
|
||
|
/**
|
||
|
* Broadcast Action: A broadcast sent by the system to indicate that {@link SafetyCenterManager}
|
||
|
* is requesting data from safety sources regarding their safety state.
|
||
|
*
|
||
|
* <p>This broadcast is sent when a user triggers a data refresh from the Safety Center UI or
|
||
|
* when Safety Center detects that its stored safety information is stale and needs to be
|
||
|
* updated.
|
||
|
*
|
||
|
* <p>This broadcast is sent explicitly to safety sources by targeting intents to a specified
|
||
|
* set of packages provided by the safety sources in the {@link SafetyCenterConfig}. The
|
||
|
* receiving components must hold the {@link
|
||
|
* android.Manifest.permission#SEND_SAFETY_CENTER_UPDATE} permission, and can use a
|
||
|
* manifest-registered receiver to be woken up by Safety Center.
|
||
|
*
|
||
|
* <p>On receiving this broadcast, safety sources should determine their safety state according
|
||
|
* to the parameters specified in the intent extras (see below) and set {@link SafetySourceData}
|
||
|
* using {@link #setSafetySourceData}, along with a {@link SafetyEvent} with {@link
|
||
|
* SafetyEvent#getType()} set to {@link SafetyEvent#SAFETY_EVENT_TYPE_REFRESH_REQUESTED} and
|
||
|
* {@link SafetyEvent#getRefreshBroadcastId()} set to the value of broadcast intent extra {@link
|
||
|
* #EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}. If the safety source is unable to provide data,
|
||
|
* it can set a {@code null} {@link SafetySourceData}, which will clear any existing {@link
|
||
|
* SafetySourceData} stored by Safety Center, and Safety Center will fall back to any
|
||
|
* placeholder data specified in {@link SafetyCenterConfig}.
|
||
|
*
|
||
|
* <p class="note">This is a protected intent that can only be sent by the system.
|
||
|
*
|
||
|
* <p>Includes the following extras:
|
||
|
*
|
||
|
* <ul>
|
||
|
* <li>{@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE}: An int representing the type of
|
||
|
* data being requested. Possible values are {@link
|
||
|
* #EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA} and {@link
|
||
|
* #EXTRA_REFRESH_REQUEST_TYPE_GET_DATA}.
|
||
|
* <li>{@link #EXTRA_REFRESH_SAFETY_SOURCE_IDS}: A {@code String[]} of ids representing the
|
||
|
* safety sources being requested for data. This extra exists for disambiguation in the
|
||
|
* case that a single component is responsible for receiving refresh requests for multiple
|
||
|
* safety sources.
|
||
|
* <li>{@link #EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID}: An unique identifier for the
|
||
|
* refresh request broadcast. This extra should be used to specify {@link
|
||
|
* SafetyEvent#getRefreshBroadcastId()} when the safety source responds to the broadcast
|
||
|
* using {@link #setSafetySourceData}.
|
||
|
* </ul>
|
||
|
*/
|
||
|
@SdkConstant(BROADCAST_INTENT_ACTION)
|
||
|
public static final String ACTION_REFRESH_SAFETY_SOURCES =
|
||
|
"android.safetycenter.action.REFRESH_SAFETY_SOURCES";
|
||
|
|
||
|
/**
|
||
|
* Used as a {@code String[]} extra field in {@link #ACTION_REFRESH_SAFETY_SOURCES} intents to
|
||
|
* specify the safety source ids of the safety sources being requested for data by Safety
|
||
|
* Center.
|
||
|
*
|
||
|
* <p>When this extra field is not specified in the intent, it is assumed that Safety Center is
|
||
|
* requesting data from all safety sources supported by the component receiving the broadcast.
|
||
|
*/
|
||
|
public static final String EXTRA_REFRESH_SAFETY_SOURCE_IDS =
|
||
|
"android.safetycenter.extra.REFRESH_SAFETY_SOURCE_IDS";
|
||
|
|
||
|
/**
|
||
|
* Used as an {@code int} extra field in {@link #ACTION_REFRESH_SAFETY_SOURCES} intents to
|
||
|
* specify the type of data request from Safety Center.
|
||
|
*
|
||
|
* <p>Possible values are {@link #EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA} and {@link
|
||
|
* #EXTRA_REFRESH_REQUEST_TYPE_GET_DATA}
|
||
|
*/
|
||
|
public static final String EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE =
|
||
|
"android.safetycenter.extra.REFRESH_SAFETY_SOURCES_REQUEST_TYPE";
|
||
|
|
||
|
/**
|
||
|
* Used as a {@code String} extra field in {@link #ACTION_REFRESH_SAFETY_SOURCES} intents to
|
||
|
* specify a string identifier for the broadcast.
|
||
|
*/
|
||
|
public static final String EXTRA_REFRESH_SAFETY_SOURCES_BROADCAST_ID =
|
||
|
"android.safetycenter.extra.REFRESH_SAFETY_SOURCES_BROADCAST_ID";
|
||
|
|
||
|
/**
|
||
|
* Used as a {@code String} extra field in {@link Intent#ACTION_SAFETY_CENTER} intents to
|
||
|
* specify an issue ID to redirect to, if applicable.
|
||
|
*
|
||
|
* <p>This extra must be used in conjunction with {@link #EXTRA_SAFETY_SOURCE_ID} as an issue ID
|
||
|
* does not uniquely identify a {@link SafetySourceIssue}. Otherwise, no redirection will occur.
|
||
|
*/
|
||
|
public static final String EXTRA_SAFETY_SOURCE_ISSUE_ID =
|
||
|
"android.safetycenter.extra.SAFETY_SOURCE_ISSUE_ID";
|
||
|
|
||
|
/**
|
||
|
* Used as a {@code String} extra field in {@link Intent#ACTION_SAFETY_CENTER} intents to
|
||
|
* specify a source ID for the {@link SafetySourceIssue} to redirect to, if applicable.
|
||
|
*
|
||
|
* <p>This extra must be used in conjunction with {@link #EXTRA_SAFETY_SOURCE_ISSUE_ID}.
|
||
|
* Otherwise, no redirection will occur.
|
||
|
*/
|
||
|
public static final String EXTRA_SAFETY_SOURCE_ID =
|
||
|
"android.safetycenter.extra.SAFETY_SOURCE_ID";
|
||
|
|
||
|
/**
|
||
|
* Used as a {@link android.os.UserHandle} extra field in {@link Intent#ACTION_SAFETY_CENTER}
|
||
|
* intents to specify a user for a given {@link SafetySourceIssue} to redirect to, if
|
||
|
* applicable.
|
||
|
*
|
||
|
* <p>This extra can be used if the same issue ID is created for multiple users (e.g. to
|
||
|
* disambiguate personal profile vs. managed profiles issues).
|
||
|
*
|
||
|
* <p>This extra can be used in conjunction with {@link #EXTRA_SAFETY_SOURCE_ISSUE_ID} and
|
||
|
* {@link #EXTRA_SAFETY_SOURCE_ID}. Otherwise, the device's primary user will be used.
|
||
|
*/
|
||
|
public static final String EXTRA_SAFETY_SOURCE_USER_HANDLE =
|
||
|
"android.safetycenter.extra.SAFETY_SOURCE_USER_HANDLE";
|
||
|
|
||
|
/**
|
||
|
* Used as a {@code String} extra field in {@link Intent#ACTION_SAFETY_CENTER} intents to
|
||
|
* specify the ID for a group of safety sources. If applicable, this will redirect to the
|
||
|
* group's corresponding subpage in the UI.
|
||
|
*/
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public static final String EXTRA_SAFETY_SOURCES_GROUP_ID =
|
||
|
"android.safetycenter.extra.SAFETY_SOURCES_GROUP_ID";
|
||
|
|
||
|
/**
|
||
|
* Used as an int value for {@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE} to indicate that
|
||
|
* the safety source should fetch fresh data relating to their safety state upon receiving a
|
||
|
* broadcast with intent action {@link #ACTION_REFRESH_SAFETY_SOURCES} and provide it to Safety
|
||
|
* Center.
|
||
|
*
|
||
|
* <p>The term "fresh" here means that the sources should ensure that the safety data is
|
||
|
* accurate as possible at the time of providing it to Safety Center, even if it involves
|
||
|
* performing an expensive and/or slow process.
|
||
|
*/
|
||
|
public static final int EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA = 0;
|
||
|
|
||
|
/**
|
||
|
* Used as an int value for {@link #EXTRA_REFRESH_SAFETY_SOURCES_REQUEST_TYPE} to indicate that
|
||
|
* upon receiving a broadcast with intent action {@link #ACTION_REFRESH_SAFETY_SOURCES}, the
|
||
|
* safety source should provide data relating to their safety state to Safety Center.
|
||
|
*
|
||
|
* <p>If the source already has its safety data cached, it may provide it without triggering a
|
||
|
* process to fetch state which may be expensive and/or slow.
|
||
|
*/
|
||
|
public static final int EXTRA_REFRESH_REQUEST_TYPE_GET_DATA = 1;
|
||
|
|
||
|
/**
|
||
|
* All possible types of data refresh requests in broadcasts with intent action {@link
|
||
|
* #ACTION_REFRESH_SAFETY_SOURCES}.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@IntDef(
|
||
|
prefix = {"EXTRA_REFRESH_REQUEST_TYPE_"},
|
||
|
value = {
|
||
|
EXTRA_REFRESH_REQUEST_TYPE_FETCH_FRESH_DATA,
|
||
|
EXTRA_REFRESH_REQUEST_TYPE_GET_DATA,
|
||
|
})
|
||
|
@Retention(RetentionPolicy.SOURCE)
|
||
|
public @interface RefreshRequestType {}
|
||
|
|
||
|
/** Indicates that the Safety Center UI has been opened by the user. */
|
||
|
public static final int REFRESH_REASON_PAGE_OPEN = 100;
|
||
|
|
||
|
/** Indicates that the rescan button in the Safety Center UI has been clicked on by the user. */
|
||
|
public static final int REFRESH_REASON_RESCAN_BUTTON_CLICK = 200;
|
||
|
|
||
|
/** Indicates that the device was rebooted. */
|
||
|
public static final int REFRESH_REASON_DEVICE_REBOOT = 300;
|
||
|
|
||
|
/** Indicates that the device locale was changed. */
|
||
|
public static final int REFRESH_REASON_DEVICE_LOCALE_CHANGE = 400;
|
||
|
|
||
|
/** Indicates that the Safety Center feature was enabled. */
|
||
|
public static final int REFRESH_REASON_SAFETY_CENTER_ENABLED = 500;
|
||
|
|
||
|
/** Indicates a generic reason for Safety Center refresh. */
|
||
|
public static final int REFRESH_REASON_OTHER = 600;
|
||
|
|
||
|
/** Indicates a periodic background refresh. */
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public static final int REFRESH_REASON_PERIODIC = 700;
|
||
|
|
||
|
/**
|
||
|
* The reason for requesting a refresh of {@link SafetySourceData} from safety sources.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
@IntDef(
|
||
|
prefix = {"REFRESH_REASON_"},
|
||
|
value = {
|
||
|
REFRESH_REASON_PAGE_OPEN,
|
||
|
REFRESH_REASON_RESCAN_BUTTON_CLICK,
|
||
|
REFRESH_REASON_DEVICE_REBOOT,
|
||
|
REFRESH_REASON_DEVICE_LOCALE_CHANGE,
|
||
|
REFRESH_REASON_SAFETY_CENTER_ENABLED,
|
||
|
REFRESH_REASON_OTHER,
|
||
|
REFRESH_REASON_PERIODIC
|
||
|
})
|
||
|
@Retention(RetentionPolicy.SOURCE)
|
||
|
@TargetApi(UPSIDE_DOWN_CAKE)
|
||
|
public @interface RefreshReason {}
|
||
|
|
||
|
/** Listener for changes to {@link SafetyCenterData}. */
|
||
|
public interface OnSafetyCenterDataChangedListener {
|
||
|
|
||
|
/**
|
||
|
* Called when {@link SafetyCenterData} tracked by the manager changes.
|
||
|
*
|
||
|
* @param data the updated data
|
||
|
*/
|
||
|
void onSafetyCenterDataChanged(@NonNull SafetyCenterData data);
|
||
|
|
||
|
/**
|
||
|
* Called when the Safety Center should display an error related to changes in its data.
|
||
|
*
|
||
|
* @param errorDetails details of an error that should be displayed to the user
|
||
|
*/
|
||
|
default void onError(@NonNull SafetyCenterErrorDetails errorDetails) {}
|
||
|
}
|
||
|
|
||
|
private final Object mListenersLock = new Object();
|
||
|
|
||
|
@GuardedBy("mListenersLock")
|
||
|
private final Map<OnSafetyCenterDataChangedListener, ListenerDelegate> mListenersToDelegates =
|
||
|
new ArrayMap<>();
|
||
|
|
||
|
@NonNull private final Context mContext;
|
||
|
@NonNull private final ISafetyCenterManager mService;
|
||
|
|
||
|
/**
|
||
|
* Creates a new instance of the {@link SafetyCenterManager}.
|
||
|
*
|
||
|
* @param context the {@link Context}
|
||
|
* @param service the {@link ISafetyCenterManager} service
|
||
|
* @hide
|
||
|
*/
|
||
|
public SafetyCenterManager(@NonNull Context context, @NonNull ISafetyCenterManager service) {
|
||
|
this.mContext = context;
|
||
|
this.mService = service;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns whether the Safety Center feature is enabled.
|
||
|
*
|
||
|
* <p>If this returns {@code false}, all the other methods in this class will no-op and/or
|
||
|
* return default values.
|
||
|
*/
|
||
|
@RequiresPermission(anyOf = {READ_SAFETY_CENTER_STATUS, SEND_SAFETY_CENTER_UPDATE})
|
||
|
public boolean isSafetyCenterEnabled() {
|
||
|
try {
|
||
|
return mService.isSafetyCenterEnabled();
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the latest {@link SafetySourceData} for a safety source, to be displayed in Safety Center
|
||
|
* UI.
|
||
|
*
|
||
|
* <p>Each {@code safetySourceId} uniquely identifies the {@link SafetySourceData} for the
|
||
|
* calling user.
|
||
|
*
|
||
|
* <p>This call will rewrite any existing {@link SafetySourceData} already set for the given
|
||
|
* {@code safetySourceId} for the calling user.
|
||
|
*
|
||
|
* @param safetySourceId the unique identifier for a safety source in the calling user
|
||
|
* @param safetySourceData the latest safety data for the safety source in the calling user. If
|
||
|
* a safety source does not have any data to set, it can set its {@link SafetySourceData} to
|
||
|
* {@code null}, in which case Safety Center will fall back to any placeholder data
|
||
|
* specified in the safety source xml configuration.
|
||
|
* @param safetyEvent the event that triggered the safety source to set safety data
|
||
|
*/
|
||
|
@RequiresPermission(SEND_SAFETY_CENTER_UPDATE)
|
||
|
public void setSafetySourceData(
|
||
|
@NonNull String safetySourceId,
|
||
|
@Nullable SafetySourceData safetySourceData,
|
||
|
@NonNull SafetyEvent safetyEvent) {
|
||
|
requireNonNull(safetySourceId, "safetySourceId cannot be null");
|
||
|
requireNonNull(safetyEvent, "safetyEvent cannot be null");
|
||
|
|
||
|
try {
|
||
|
mService.setSafetySourceData(
|
||
|
safetySourceId,
|
||
|
safetySourceData,
|
||
|
safetyEvent,
|
||
|
mContext.getPackageName(),
|
||
|
mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the latest {@link SafetySourceData} set through {@link #setSafetySourceData} for the
|
||
|
* given {@code safetySourceId} and calling user.
|
||
|
*
|
||
|
* <p>Returns {@code null} if there never was any data sent for the given {@code safetySourceId}
|
||
|
* and user.
|
||
|
*/
|
||
|
@RequiresPermission(SEND_SAFETY_CENTER_UPDATE)
|
||
|
@Nullable
|
||
|
public SafetySourceData getSafetySourceData(@NonNull String safetySourceId) {
|
||
|
requireNonNull(safetySourceId, "safetySourceId cannot be null");
|
||
|
|
||
|
try {
|
||
|
return mService.getSafetySourceData(
|
||
|
safetySourceId, mContext.getPackageName(), mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Notifies the Safety Center of an error related to a given safety source.
|
||
|
*
|
||
|
* <p>Safety sources should use this API to notify Safety Center when Safety Center requested or
|
||
|
* expected them to perform an action or provide data, but they were unable to do so.
|
||
|
*
|
||
|
* @param safetySourceId the id of the safety source that provided the issue
|
||
|
* @param safetySourceErrorDetails details of the error that occurred
|
||
|
*/
|
||
|
@RequiresPermission(SEND_SAFETY_CENTER_UPDATE)
|
||
|
public void reportSafetySourceError(
|
||
|
@NonNull String safetySourceId,
|
||
|
@NonNull SafetySourceErrorDetails safetySourceErrorDetails) {
|
||
|
requireNonNull(safetySourceId, "safetySourceId cannot be null");
|
||
|
requireNonNull(safetySourceErrorDetails, "safetySourceErrorDetails cannot be null");
|
||
|
|
||
|
try {
|
||
|
mService.reportSafetySourceError(
|
||
|
safetySourceId,
|
||
|
safetySourceErrorDetails,
|
||
|
mContext.getPackageName(),
|
||
|
mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Requests safety sources to set their latest {@link SafetySourceData} for Safety Center.
|
||
|
*
|
||
|
* <p>This API sends a broadcast to all safety sources with action {@link
|
||
|
* #ACTION_REFRESH_SAFETY_SOURCES}. See {@link #ACTION_REFRESH_SAFETY_SOURCES} for details on
|
||
|
* how safety sources should respond to receiving these broadcasts.
|
||
|
*
|
||
|
* @param refreshReason the reason for the refresh
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
public void refreshSafetySources(@RefreshReason int refreshReason) {
|
||
|
try {
|
||
|
mService.refreshSafetySources(refreshReason, mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Requests a specific subset of safety sources to set their latest {@link SafetySourceData} for
|
||
|
* Safety Center.
|
||
|
*
|
||
|
* <p>This API sends a broadcast to safety sources with action {@link
|
||
|
* #ACTION_REFRESH_SAFETY_SOURCES} and {@link #EXTRA_REFRESH_SAFETY_SOURCE_IDS} to specify the
|
||
|
* IDs of safety sources being requested for data by Safety Center.
|
||
|
*
|
||
|
* <p>This API is an overload of {@link #refreshSafetySources(int)} and is used to request data
|
||
|
* from safety sources that are part of a subpage in the Safety Center UI.
|
||
|
*
|
||
|
* @see #refreshSafetySources(int)
|
||
|
* @param refreshReason the reason for the refresh
|
||
|
* @param safetySourceIds list of IDs for the safety sources being refreshed
|
||
|
* @throws UnsupportedOperationException if accessed from a version lower than {@link
|
||
|
* UPSIDE_DOWN_CAKE}
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
@RequiresApi(UPSIDE_DOWN_CAKE)
|
||
|
public void refreshSafetySources(
|
||
|
@RefreshReason int refreshReason, @NonNull List<String> safetySourceIds) {
|
||
|
if (!SdkLevel.isAtLeastU()) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"Method not supported on versions lower than UPSIDE_DOWN_CAKE");
|
||
|
}
|
||
|
|
||
|
requireNonNull(safetySourceIds, "safetySourceIds cannot be null");
|
||
|
|
||
|
try {
|
||
|
mService.refreshSpecificSafetySources(
|
||
|
refreshReason, mContext.getUser().getIdentifier(), safetySourceIds);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Returns the current {@link SafetyCenterConfig}, if available. */
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
@Nullable
|
||
|
public SafetyCenterConfig getSafetyCenterConfig() {
|
||
|
try {
|
||
|
return mService.getSafetyCenterConfig();
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the current {@link SafetyCenterData}, assembled from {@link SafetySourceData} from
|
||
|
* all sources.
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
@NonNull
|
||
|
public SafetyCenterData getSafetyCenterData() {
|
||
|
try {
|
||
|
return mService.getSafetyCenterData(
|
||
|
mContext.getPackageName(), mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds a listener for changes to {@link SafetyCenterData}.
|
||
|
*
|
||
|
* @see #removeOnSafetyCenterDataChangedListener(OnSafetyCenterDataChangedListener)
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
public void addOnSafetyCenterDataChangedListener(
|
||
|
@NonNull @CallbackExecutor Executor executor,
|
||
|
@NonNull OnSafetyCenterDataChangedListener listener) {
|
||
|
requireNonNull(executor, "executor cannot be null");
|
||
|
requireNonNull(listener, "listener cannot be null");
|
||
|
|
||
|
synchronized (mListenersLock) {
|
||
|
if (mListenersToDelegates.containsKey(listener)) return;
|
||
|
|
||
|
ListenerDelegate delegate = new ListenerDelegate(executor, listener);
|
||
|
try {
|
||
|
mService.addOnSafetyCenterDataChangedListener(
|
||
|
delegate, mContext.getPackageName(), mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
mListenersToDelegates.put(listener, delegate);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Removes a listener for changes to {@link SafetyCenterData}.
|
||
|
*
|
||
|
* @see #addOnSafetyCenterDataChangedListener(Executor, OnSafetyCenterDataChangedListener)
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
public void removeOnSafetyCenterDataChangedListener(
|
||
|
@NonNull OnSafetyCenterDataChangedListener listener) {
|
||
|
requireNonNull(listener, "listener cannot be null");
|
||
|
|
||
|
synchronized (mListenersLock) {
|
||
|
ListenerDelegate delegate = mListenersToDelegates.get(listener);
|
||
|
if (delegate == null) return;
|
||
|
|
||
|
try {
|
||
|
mService.removeOnSafetyCenterDataChangedListener(
|
||
|
delegate, mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
delegate.markAsRemoved();
|
||
|
mListenersToDelegates.remove(listener);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Dismiss a Safety Center issue and prevent it from affecting the overall safety status.
|
||
|
*
|
||
|
* @param safetyCenterIssueId the target issue ID returned by {@link SafetyCenterIssue#getId()}
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
public void dismissSafetyCenterIssue(@NonNull String safetyCenterIssueId) {
|
||
|
requireNonNull(safetyCenterIssueId, "safetyCenterIssueId cannot be null");
|
||
|
|
||
|
try {
|
||
|
mService.dismissSafetyCenterIssue(
|
||
|
safetyCenterIssueId, mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Executes the specified Safety Center issue action on the specified Safety Center issue.
|
||
|
*
|
||
|
* @param safetyCenterIssueId the target issue ID returned by {@link SafetyCenterIssue#getId()}
|
||
|
* @param safetyCenterIssueActionId the target action ID returned by {@link
|
||
|
* SafetyCenterIssue.Action#getId()}
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
public void executeSafetyCenterIssueAction(
|
||
|
@NonNull String safetyCenterIssueId, @NonNull String safetyCenterIssueActionId) {
|
||
|
requireNonNull(safetyCenterIssueId, "safetyCenterIssueId cannot be null");
|
||
|
requireNonNull(safetyCenterIssueActionId, "safetyCenterIssueActionId cannot be null");
|
||
|
|
||
|
try {
|
||
|
mService.executeSafetyCenterIssueAction(
|
||
|
safetyCenterIssueId,
|
||
|
safetyCenterIssueActionId,
|
||
|
mContext.getUser().getIdentifier());
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clears all {@link SafetySourceData} (set by safety sources using {@link
|
||
|
* #setSafetySourceData}) for testing.
|
||
|
*
|
||
|
* <p>Note: This API serves to facilitate CTS testing and should not be used for other purposes.
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
public void clearAllSafetySourceDataForTests() {
|
||
|
try {
|
||
|
mService.clearAllSafetySourceDataForTests();
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Overrides the {@link SafetyCenterConfig} for testing.
|
||
|
*
|
||
|
* <p>When set, the overridden {@link SafetyCenterConfig} will be used instead of the {@link
|
||
|
* SafetyCenterConfig} parsed from the XML file to read configured safety sources.
|
||
|
*
|
||
|
* <p>Note: This API serves to facilitate CTS testing and should not be used to configure safety
|
||
|
* sources dynamically for production. Once used for testing, the override should be cleared.
|
||
|
*
|
||
|
* @see #clearSafetyCenterConfigForTests()
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
public void setSafetyCenterConfigForTests(@NonNull SafetyCenterConfig safetyCenterConfig) {
|
||
|
requireNonNull(safetyCenterConfig, "safetyCenterConfig cannot be null");
|
||
|
|
||
|
try {
|
||
|
mService.setSafetyCenterConfigForTests(safetyCenterConfig);
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Clears the override of the {@link SafetyCenterConfig} set for testing.
|
||
|
*
|
||
|
* <p>Once cleared, the {@link SafetyCenterConfig} parsed from the XML file will be used to read
|
||
|
* configured safety sources.
|
||
|
*
|
||
|
* <p>Note: This API serves to facilitate CTS testing and should not be used for other purposes.
|
||
|
*
|
||
|
* @see #setSafetyCenterConfigForTests(SafetyCenterConfig)
|
||
|
*/
|
||
|
@RequiresPermission(MANAGE_SAFETY_CENTER)
|
||
|
public void clearSafetyCenterConfigForTests() {
|
||
|
try {
|
||
|
mService.clearSafetyCenterConfigForTests();
|
||
|
} catch (RemoteException e) {
|
||
|
throw e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static final class ListenerDelegate extends IOnSafetyCenterDataChangedListener.Stub {
|
||
|
@NonNull private final Executor mExecutor;
|
||
|
@NonNull private final OnSafetyCenterDataChangedListener mOriginalListener;
|
||
|
|
||
|
private volatile boolean mRemoved = false;
|
||
|
|
||
|
private ListenerDelegate(
|
||
|
@NonNull Executor executor,
|
||
|
@NonNull OnSafetyCenterDataChangedListener originalListener) {
|
||
|
mExecutor = executor;
|
||
|
mOriginalListener = originalListener;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onSafetyCenterDataChanged(@NonNull SafetyCenterData safetyCenterData) {
|
||
|
requireNonNull(safetyCenterData, "safetyCenterData cannot be null");
|
||
|
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mExecutor.execute(
|
||
|
() -> {
|
||
|
if (mRemoved) {
|
||
|
return;
|
||
|
}
|
||
|
// The remove call could still complete at this point, but we're ok
|
||
|
// with this raciness on separate threads. If the listener is removed on
|
||
|
// the same thread as `mExecutor`; the above check should ensure that
|
||
|
// the listener won't be called after the remove call.
|
||
|
mOriginalListener.onSafetyCenterDataChanged(safetyCenterData);
|
||
|
});
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void onError(@NonNull SafetyCenterErrorDetails safetyCenterErrorDetails) {
|
||
|
requireNonNull(safetyCenterErrorDetails, "safetyCenterErrorDetails cannot be null");
|
||
|
|
||
|
final long identity = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mExecutor.execute(
|
||
|
() -> {
|
||
|
if (mRemoved) {
|
||
|
return;
|
||
|
}
|
||
|
// The remove call could still complete at this point, but we're ok
|
||
|
// with this raciness on separate threads. If the listener is removed on
|
||
|
// the same thread as `mExecutor`; the above check should ensure that
|
||
|
// the listener won't be called after the remove call.
|
||
|
mOriginalListener.onError(safetyCenterErrorDetails);
|
||
|
});
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(identity);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void markAsRemoved() {
|
||
|
mRemoved = true;
|
||
|
}
|
||
|
}
|
||
|
}
|