1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.jobscheduler.cts.jobtestapp;
18 
19 import android.app.AlarmManager;
20 import android.app.Notification;
21 import android.app.NotificationChannel;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.app.job.JobInfo;
25 import android.app.job.JobScheduler;
26 import android.app.job.JobService;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.os.Bundle;
32 import android.os.SystemClock;
33 import android.util.Log;
34 
35 /**
36  * Schedules jobs for this package but does not, by itself, occupy a foreground uid state
37  * while doing so.
38  */
39 public class TestJobSchedulerReceiver extends BroadcastReceiver {
40     private static final String TAG = TestJobSchedulerReceiver.class.getSimpleName();
41     static final String PACKAGE_NAME = "android.jobscheduler.cts.jobtestapp";
42     private static final String NOTIFICATION_CHANNEL_ID = TAG + "_channel";
43 
44     public static final long NO_DEADLINE = -1L;
45     // Don't use JobInfo.PRIORITY_DEFAULT because that may cause issues if the job is meant to be
46     // expedited or user-initiated.
47     public static final int NO_PRIORITY = -1;
48 
49     public static final String ACTION_JOB_SCHEDULE_RESULT =
50             PACKAGE_NAME + ".action.SCHEDULE_RESULT";
51     public static final String EXTRA_SCHEDULE_RESULT = PACKAGE_NAME + ".extra.SCHEDULE_RESULT";
52 
53     public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
54     public static final String EXTRA_ALLOW_IN_IDLE = PACKAGE_NAME + ".extra.ALLOW_IN_IDLE";
55     public static final String EXTRA_DEADLINE = PACKAGE_NAME + ".extra.DEADLINE";
56     public static final String EXTRA_REQUIRED_NETWORK_TYPE =
57             PACKAGE_NAME + ".extra.REQUIRED_NETWORK_TYPE";
58     public static final String EXTRA_REQUIRES_STORAGE_NOT_LOW =
59             PACKAGE_NAME + ".extra.REQUIRES_STORAGE_NOT_LOW";
60     public static final String EXTRA_AS_EXPEDITED = PACKAGE_NAME + ".extra.AS_EXPEDITED";
61     public static final String EXTRA_AS_USER_INITIATED = PACKAGE_NAME + ".extra.AS_USER_INITIATED";
62     public static final String EXTRA_PRIORITY = ".extra.PRIORITY";
63     public static final String EXTRA_REQUEST_JOB_UID_STATE =
64             PACKAGE_NAME + ".extra.REQUEST_JOB_UID_STATE";
65     public static final String EXTRA_SET_NOTIFICATION = PACKAGE_NAME + ".extra.SET_NOTIFICATION";
66     public static final String EXTRA_SET_NOTIFICATION_JOB_END_POLICY =
67             PACKAGE_NAME + ".extra.SET_NOTIFICATION_JOB_END_POLICY";
68     public static final String EXTRA_SLOW_START = PACKAGE_NAME + ".extra.SLOW_START";
69     public static final String EXTRA_SLOW_STOP = PACKAGE_NAME + ".extra.SLOW_STOP";
70     public static final String ACTION_SCHEDULE_JOB = PACKAGE_NAME + ".action.SCHEDULE_JOB";
71     public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
72     public static final String ACTION_POST_UI_INITIATING_NOTIFICATION =
73             PACKAGE_NAME + ".action.POST_UI_INITIATING_NOTIFICATION";
74     public static final String ACTION_SCHEDULE_FGS_START_ALARM =
75             PACKAGE_NAME + ".action.SCHEDULE_FGS_START_ALARM";
76     public static final String ACTION_START_FGS = PACKAGE_NAME + ".action.START_FGS";
77     public static final String ACTION_NOTIFICATION_POSTED =
78             PACKAGE_NAME + ".action.NOTIFICATION_POSTED";
79     public static final String ACTION_ALARM_SCHEDULED =
80             PACKAGE_NAME + ".action.ALARM_SCHEDULED";
81     public static final int JOB_INITIAL_BACKOFF = 10_000;
82 
83     @Override
onReceive(Context context, Intent intent)84     public void onReceive(Context context, Intent intent) {
85         Log.d(TAG, "Processing intent: " + intent.getAction());
86         final ComponentName jobServiceComponent = new ComponentName(context, TestJobService.class);
87         final JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
88         switch (intent.getAction()) {
89             case ACTION_CANCEL_JOBS:
90                 jobScheduler.cancelAll();
91                 Log.d(TAG, "Cancelled all jobs for " + context.getPackageName());
92                 break;
93             case ACTION_SCHEDULE_JOB:
94                 final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
95                 final boolean allowInIdle = intent.getBooleanExtra(EXTRA_ALLOW_IN_IDLE, false);
96                 final int networkType =
97                         intent.getIntExtra(EXTRA_REQUIRED_NETWORK_TYPE, JobInfo.NETWORK_TYPE_NONE);
98                 final boolean expedited = intent.getBooleanExtra(EXTRA_AS_EXPEDITED, false);
99                 final boolean storageNotLow =
100                         intent.getBooleanExtra(EXTRA_REQUIRES_STORAGE_NOT_LOW, false);
101                 final long deadline = intent.getLongExtra(EXTRA_DEADLINE, NO_DEADLINE);
102                 final int priority = intent.getIntExtra(EXTRA_PRIORITY, NO_PRIORITY);
103                 final boolean userInitiated =
104                         intent.getBooleanExtra(EXTRA_AS_USER_INITIATED, false);
105                 final int backoffPolicy = userInitiated ? JobInfo.BACKOFF_POLICY_EXPONENTIAL
106                                                         : JobInfo.BACKOFF_POLICY_LINEAR;
107                 final boolean requestJobUidState = intent.getBooleanExtra(
108                         EXTRA_REQUEST_JOB_UID_STATE, false);
109                 final Bundle extras = new Bundle();
110                 extras.putBoolean(EXTRA_REQUEST_JOB_UID_STATE, requestJobUidState);
111                 extras.putBoolean(EXTRA_SET_NOTIFICATION,
112                         intent.getBooleanExtra(EXTRA_SET_NOTIFICATION, false));
113                 extras.putInt(EXTRA_SET_NOTIFICATION_JOB_END_POLICY,
114                         intent.getIntExtra(EXTRA_SET_NOTIFICATION_JOB_END_POLICY,
115                                 JobService.JOB_END_NOTIFICATION_POLICY_REMOVE));
116                 extras.putBoolean(EXTRA_SLOW_START,
117                         intent.getBooleanExtra(EXTRA_SLOW_START, false));
118                 extras.putBoolean(EXTRA_SLOW_STOP, intent.getBooleanExtra(EXTRA_SLOW_STOP, false));
119                 JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
120                         .setBackoffCriteria(JOB_INITIAL_BACKOFF, backoffPolicy)
121                         .setTransientExtras(extras)
122                         .setImportantWhileForeground(allowInIdle)
123                         .setExpedited(expedited)
124                         .setUserInitiated(userInitiated)
125                         .setRequiredNetworkType(networkType)
126                         .setRequiresStorageNotLow(storageNotLow);
127                 if (deadline != NO_DEADLINE) {
128                     jobBuilder.setOverrideDeadline(deadline);
129                 }
130                 if (priority != NO_PRIORITY) {
131                     jobBuilder.setPriority(priority);
132                 }
133                 final int result = jobScheduler.schedule(jobBuilder.build());
134                 if (result != JobScheduler.RESULT_SUCCESS) {
135                     Log.e(TAG, "Could not schedule job " + jobId);
136                 } else {
137                     Log.d(TAG, "Successfully scheduled job with id " + jobId);
138                 }
139 
140                 final Intent scheduleJobResultIntent = new Intent(ACTION_JOB_SCHEDULE_RESULT);
141                 scheduleJobResultIntent.putExtra(EXTRA_JOB_ID_KEY, jobId);
142                 scheduleJobResultIntent.putExtra(EXTRA_SCHEDULE_RESULT, result);
143                 context.sendBroadcast(scheduleJobResultIntent);
144                 break;
145             case ACTION_POST_UI_INITIATING_NOTIFICATION:
146                 NotificationManager notificationManager =
147                         context.getSystemService(NotificationManager.class);
148                 NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
149                         TestJobSchedulerReceiver.class.getSimpleName(),
150                         NotificationManager.IMPORTANCE_DEFAULT);
151                 notificationManager.createNotificationChannel(channel);
152 
153                 final Intent scheduleJobIntent = new Intent(intent);
154                 scheduleJobIntent.setAction(ACTION_SCHEDULE_JOB);
155 
156                 final PendingIntent scheduleJobPendingIntent = PendingIntent.getBroadcast(
157                         context, 1, scheduleJobIntent,
158                         PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
159                 Notification notification =
160                         new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
161                                 .setContentTitle("Test title")
162                                 .setSmallIcon(android.R.mipmap.sym_def_app_icon)
163                                 .setContentText("Click to start UI job!")
164                                 .setContentIntent(scheduleJobPendingIntent)
165                                 .build();
166                 notificationManager.notify(0, notification);
167 
168                 final Intent completionIntent = new Intent(ACTION_NOTIFICATION_POSTED);
169                 context.sendBroadcast(completionIntent);
170                 break;
171             case ACTION_SCHEDULE_FGS_START_ALARM: {
172                 final Intent startFgsIntent = new Intent(ACTION_START_FGS);
173                 ComponentName testComponentName = new ComponentName(context, TestFgsService.class);
174                 startFgsIntent.setComponent(testComponentName);
175 
176                 PendingIntent startFgsPendingIntent = PendingIntent.getForegroundService(
177                         context, 2, startFgsIntent,
178                         PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
179 
180                 AlarmManager alarmManager = context.getSystemService(AlarmManager.class);
181                 alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
182                         SystemClock.elapsedRealtime(), // Get the alarm to fire immediately
183                         startFgsPendingIntent);
184 
185                 final Intent alarmScheduledIntent = new Intent(ACTION_ALARM_SCHEDULED);
186                 context.sendBroadcast(alarmScheduledIntent);
187                 break;
188             }
189             case ACTION_START_FGS: {
190                 final Intent startFgsIntent = new Intent();
191                 ComponentName testComponentName = new ComponentName(context, TestFgsService.class);
192                 startFgsIntent.setComponent(testComponentName);
193 
194                 context.startForegroundService(startFgsIntent);
195                 break;
196             }
197             default:
198                 Log.e(TAG, "Unknown action " + intent.getAction());
199         }
200     }
201 }
202