| /* |
| * Copyright (C) 2025 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.accessibility; |
| |
| import static com.android.internal.accessibility.common.NotificationConstants.EXTRA_PAGE_ID; |
| |
| import android.app.job.JobInfo; |
| import android.app.job.JobParameters; |
| import android.app.job.JobScheduler; |
| import android.app.job.JobService; |
| import android.app.settings.SettingsEnums; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.os.PersistableBundle; |
| import android.util.Slog; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.VisibleForTesting; |
| |
| import com.android.settings.accessibility.notification.NotificationHelper; |
| |
| /** |
| * JobService implementation for scheduling a notification to inform users about an accessibility |
| * survey and guide them to review it. |
| * |
| * <p>This service ensures that the notification is displayed after a specified delay. |
| */ |
| public class AccessibilitySurveyNotificationJobService extends JobService { |
| |
| public static final String TAG = "AccessibilitySurveyNotificationJobService"; |
| |
| // The base ID for job is derived from the bug component ID. |
| // Page-specific notification IDs are generated by adding the respective pageId to this base. |
| private static final int JOB_ID_BASE = 751131; |
| |
| @Nullable |
| private NotificationHelper mNotificationHelper; |
| |
| /** |
| * Default constructor for {@link AccessibilitySurveyNotificationJobService}. |
| */ |
| public AccessibilitySurveyNotificationJobService() {} |
| |
| /** |
| * Constructor for {@link AccessibilitySurveyNotificationJobService} for testing purposes. |
| * |
| * @param notificationHelper The {@link NotificationHelper} instance to use, or {@code null} if |
| * it should be initialized lazily. |
| */ |
| @VisibleForTesting |
| public AccessibilitySurveyNotificationJobService( |
| @Nullable NotificationHelper notificationHelper) { |
| mNotificationHelper = notificationHelper; |
| } |
| |
| /** |
| * Schedule a new job that will show a notification after the specified amount of time. |
| * If a job with the same ID is already pending, a new one will not be scheduled. |
| * |
| * @param context The application context. |
| * @param pageId The identifier for the survey page to be shown. |
| * @param delayMillis The minimum time in milliseconds to wait before showing the notification. |
| */ |
| public void scheduleJob(@NonNull Context context, int pageId, long delayMillis) { |
| final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); |
| if (jobScheduler == null) { |
| return; |
| } |
| |
| int jobId = getJobId(pageId); |
| if (jobScheduler.getPendingJob(jobId) == null) { |
| final ComponentName component = new ComponentName( |
| context, AccessibilitySurveyNotificationJobService.class); |
| |
| final PersistableBundle bundle = new PersistableBundle(); |
| bundle.putInt(EXTRA_PAGE_ID, pageId); |
| |
| final JobInfo newJob = new JobInfo.Builder(jobId, component) |
| .setMinimumLatency(delayMillis) |
| .setExtras(bundle) |
| .build(); |
| |
| int scheduleResult = jobScheduler.schedule(newJob); |
| if (scheduleResult == JobScheduler.RESULT_SUCCESS) { |
| Slog.i(TAG, "Job successfully scheduled for pageId: " + pageId + " to run " |
| + "after " + delayMillis + "ms."); |
| } else { |
| Slog.e(TAG, "Job scheduling failed for pageId: " + pageId + " with result code: " |
| + scheduleResult); |
| } |
| } |
| } |
| |
| /** |
| * Cancels a previously scheduled job. |
| * |
| * @param context The application context. |
| * @param pageId The identifier for the survey page whose job is to be cancelled. |
| */ |
| public void cancelJob(@NonNull Context context, int pageId) { |
| final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); |
| if (jobScheduler == null) { |
| return; |
| } |
| |
| int jobId = getJobId(pageId); |
| if (jobScheduler.getPendingJob(jobId) != null) { |
| jobScheduler.cancel(jobId); |
| } |
| } |
| |
| @Override |
| public boolean onStartJob(@NonNull JobParameters params) { |
| final PersistableBundle extras = params.getExtras(); |
| if (extras == null) { |
| return false; |
| } |
| |
| int pageId = extras.getInt(EXTRA_PAGE_ID, SettingsEnums.PAGE_UNKNOWN); |
| if (pageId == SettingsEnums.PAGE_UNKNOWN) { |
| return false; |
| } |
| |
| Slog.i(TAG, "Job starting for pageId: " + pageId); |
| if (mNotificationHelper == null) { |
| mNotificationHelper = new NotificationHelper(this); |
| } |
| mNotificationHelper.showSurveyNotification(pageId); |
| |
| // Returning false here indicates that the job is finished immediately. The system will |
| // release the wakelock, and onStopJob will not be invoked. |
| return false; |
| } |
| |
| @Override |
| public boolean onStopJob(@NonNull JobParameters params) { |
| Slog.w(TAG, "Job stopped by system. Job ID: " + params.getJobId() + ", reason: " |
| + params.getStopReason()); |
| // Called if the system stops the job, e.g., due to preemption before or during |
| // onStartJob's brief execution (as onStartJob returns false quickly). |
| // Returning true attempts to reschedule the job with its original criteria. |
| return true; |
| } |
| |
| /** |
| * Generates a job ID based on a base ID and a page ID. This ensures that job for different |
| * pages will have distinct IDs. |
| * |
| * @param pageId The unique identifier of the page for which to generate a job ID. |
| * @return A unique integer representing the job ID for the given page. |
| */ |
| @VisibleForTesting |
| public int getJobId(int pageId) { |
| return JOB_ID_BASE + pageId; |
| } |
| } |