1 /*
2  * Copyright (C) 2018 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.app.stubs;
18 
19 import android.app.Activity;
20 import android.app.ActivityManager;
21 import android.app.ForegroundServiceStartNotAllowedException;
22 import android.app.PendingIntent;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.os.Bundle;
29 import android.os.IBinder;
30 import android.os.Parcel;
31 import android.util.ArrayMap;
32 import android.util.Log;
33 
34 import java.util.concurrent.TimeUnit;
35 
36 public class CommandReceiver extends BroadcastReceiver {
37 
38     private static final String TAG = "CommandReceiver";
39 
40     // Requires flags and targetPackage
41     public static final int COMMAND_BIND_SERVICE = 1;
42     // Requires targetPackage
43     public static final int COMMAND_UNBIND_SERVICE = 2;
44     public static final int COMMAND_START_FOREGROUND_SERVICE = 3;
45     public static final int COMMAND_STOP_FOREGROUND_SERVICE = 4;
46     public static final int COMMAND_START_FOREGROUND_SERVICE_LOCATION = 5;
47     public static final int COMMAND_STOP_FOREGROUND_SERVICE_LOCATION = 6;
48     public static final int COMMAND_START_ALERT_SERVICE = 7;
49     public static final int COMMAND_STOP_ALERT_SERVICE = 8;
50     public static final int COMMAND_SELF_INDUCED_ANR = 9;
51     public static final int COMMAND_START_ACTIVITY = 10;
52     public static final int COMMAND_STOP_ACTIVITY = 11;
53     public static final int COMMAND_CREATE_FGSL_PENDING_INTENT = 12;
54     public static final int COMMAND_SEND_FGSL_PENDING_INTENT = 13;
55     public static final int COMMAND_BIND_FOREGROUND_SERVICE = 14;
56     public static final int COMMAND_START_CHILD_PROCESS = 15;
57     public static final int COMMAND_STOP_CHILD_PROCESS = 16;
58     public static final int COMMAND_WAIT_FOR_CHILD_PROCESS_GONE = 17;
59     public static final int COMMAND_START_SERVICE = 18;
60     public static final int COMMAND_STOP_SERVICE = 19;
61     public static final int COMMAND_START_FOREGROUND_SERVICE_STICKY = 20;
62     public static final int COMMAND_STOP_FOREGROUND_SERVICE_STICKY = 21;
63     public static final int COMMAND_EMPTY = 22;
64 
65     public static final int RESULT_CHILD_PROCESS_STARTED = IBinder.FIRST_CALL_TRANSACTION;
66     public static final int RESULT_CHILD_PROCESS_STOPPED = IBinder.FIRST_CALL_TRANSACTION + 1;
67     public static final int RESULT_CHILD_PROCESS_GONE = IBinder.FIRST_CALL_TRANSACTION + 2;
68 
69     public static final String EXTRA_COMMAND = "android.app.stubs.extra.COMMAND";
70     public static final String EXTRA_TARGET_PACKAGE = "android.app.stubs.extra.TARGET_PACKAGE";
71     public static final String EXTRA_FLAGS = "android.app.stubs.extra.FLAGS";
72     public static final String EXTRA_CALLBACK = "android.app.stubs.extra.callback";
73     public static final String EXTRA_CHILD_CMDLINE = "android.app.stubs.extra.child_cmdline";
74     public static final String EXTRA_TIMEOUT = "android.app.stubs.extra.child_cmdline";
75     public static final String EXTRA_MESSENGER = "android.app.stubs.extra.EXTRA_MESSENGER";
76 
77     public static final String SERVICE_NAME = "android.app.stubs.LocalService";
78     public static final String FG_SERVICE_NAME = "android.app.stubs.LocalForegroundService";
79     public static final String FG_LOCATION_SERVICE_NAME =
80             "android.app.stubs.LocalForegroundServiceLocation";
81     public static final String FG_STICKY_SERVICE_NAME =
82             "android.app.stubs.LocalForegroundServiceSticky";
83 
84     public static final String ACTIVITY_NAME = "android.app.stubs.SimpleActivity";
85 
86     private static ArrayMap<String,ServiceConnection> sServiceMap = new ArrayMap<>();
87 
88     // Map a packageName to a Intent that starts an Activity.
89     private static ArrayMap<String, Intent> sActivityIntent = new ArrayMap<>();
90 
91     // Map a packageName to a PendingIntent.
92     private static ArrayMap<String, PendingIntent> sPendingIntent = new ArrayMap<>();
93 
94     /** The child process, started via {@link #COMMAND_START_CHILD_PROCESS} */
95     private static Process sChildProcess;
96 
97     /**
98      * Handle the different types of binding/unbinding requests.
99      * @param context The Context in which the receiver is running.
100      * @param intent The Intent being received.
101      */
102     @Override
onReceive(Context context, Intent intent)103     public void onReceive(Context context, Intent intent) {
104         // Use the application context as the receiver context could be restricted.
105         context = context.getApplicationContext();
106         int command = intent.getIntExtra(EXTRA_COMMAND, -1);
107         Log.d(TAG + "_" + context.getPackageName(), "Got command " + command + ", intent="
108                 + intent);
109         switch (command) {
110             case COMMAND_BIND_SERVICE:
111                 doBindService(context, intent, SERVICE_NAME);
112                 break;
113             case COMMAND_UNBIND_SERVICE:
114                 doUnbindService(context, intent);
115                 break;
116             case COMMAND_START_FOREGROUND_SERVICE:
117                 doStartForegroundService(context, intent);
118                 break;
119             case COMMAND_START_SERVICE:
120                 doStartService(context, intent);
121                 break;
122             case COMMAND_STOP_FOREGROUND_SERVICE:
123             case COMMAND_STOP_SERVICE:
124                 doStopService(context, intent, FG_SERVICE_NAME);
125                 break;
126             case COMMAND_START_FOREGROUND_SERVICE_LOCATION:
127                 doStartForegroundServiceWithType(context, intent);
128                 break;
129             case COMMAND_STOP_FOREGROUND_SERVICE_LOCATION:
130                 doStopService(context, intent, FG_LOCATION_SERVICE_NAME);
131                 break;
132             case COMMAND_START_FOREGROUND_SERVICE_STICKY:
133                 doStartForegroundServiceSticky(context, intent);
134                 break;
135             case COMMAND_STOP_FOREGROUND_SERVICE_STICKY:
136                 doStopService(context, intent, FG_STICKY_SERVICE_NAME);
137                 break;
138             case COMMAND_START_ALERT_SERVICE:
139                 doStartAlertService(context);
140                 break;
141             case COMMAND_STOP_ALERT_SERVICE:
142                 doStopAlertService(context);
143                 break;
144             case COMMAND_SELF_INDUCED_ANR:
145                 doSelfInducedAnr(context);
146                 break;
147             case COMMAND_START_ACTIVITY:
148                 doStartActivity(context, intent);
149                 break;
150             case COMMAND_STOP_ACTIVITY:
151                 doStopActivity(context, intent);
152                 break;
153             case COMMAND_CREATE_FGSL_PENDING_INTENT:
154                 doCreateFgslPendingIntent(context, intent);
155                 break;
156             case COMMAND_SEND_FGSL_PENDING_INTENT:
157                 doSendFgslPendingIntent(context, intent);
158                 break;
159             case COMMAND_BIND_FOREGROUND_SERVICE:
160                 doBindService(context, intent, FG_LOCATION_SERVICE_NAME);
161                 break;
162             case COMMAND_START_CHILD_PROCESS:
163                 doStartChildProcess(context, intent);
164                 break;
165             case COMMAND_STOP_CHILD_PROCESS:
166                 doStopChildProcess(context, intent);
167                 break;
168             case COMMAND_WAIT_FOR_CHILD_PROCESS_GONE:
169                 doWaitForChildProcessGone(context, intent);
170                 break;
171             case COMMAND_EMPTY:
172                 break;
173         }
174     }
175 
doBindService(Context context, Intent commandIntent, String serviceName)176     private void doBindService(Context context, Intent commandIntent, String serviceName) {
177         String targetPackage = getTargetPackage(commandIntent);
178         int flags = getFlags(commandIntent);
179 
180         Intent bindIntent = new Intent();
181         bindIntent.setComponent(new ComponentName(targetPackage, serviceName));
182 
183         ServiceConnection connection = addServiceConnection(targetPackage);
184 
185         context.bindService(bindIntent, connection, flags | Context.BIND_AUTO_CREATE);
186     }
187 
doUnbindService(Context context, Intent commandIntent)188     private void doUnbindService(Context context, Intent commandIntent) {
189         String targetPackage = getTargetPackage(commandIntent);
190         context.unbindService(sServiceMap.remove(targetPackage));
191     }
192 
doStartForegroundService(Context context, Intent commandIntent)193     private void doStartForegroundService(Context context, Intent commandIntent) {
194         String targetPackage = getTargetPackage(commandIntent);
195         Intent fgsIntent = new Intent();
196         fgsIntent.putExtras(commandIntent);
197         fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME));
198         int command = LocalForegroundService.COMMAND_START_FOREGROUND;
199         fgsIntent.putExtras(LocalForegroundService.newCommand(command));
200         try {
201             context.startForegroundService(fgsIntent);
202         } catch (ForegroundServiceStartNotAllowedException e) {
203             Log.d(TAG, "startForegroundService gets an "
204                     + " ForegroundServiceStartNotAllowedException", e);
205         }
206     }
207 
doStartService(Context context, Intent commandIntent)208     private void doStartService(Context context, Intent commandIntent) {
209         String targetPackage = getTargetPackage(commandIntent);
210         Intent fgsIntent = new Intent();
211         fgsIntent.putExtras(commandIntent);
212         fgsIntent.setComponent(new ComponentName(targetPackage, FG_SERVICE_NAME));
213         context.startService(fgsIntent);
214     }
215 
doStartForegroundServiceWithType(Context context, Intent commandIntent)216     private void doStartForegroundServiceWithType(Context context, Intent commandIntent) {
217         String targetPackage = getTargetPackage(commandIntent);
218         Intent fgsIntent = new Intent();
219         fgsIntent.putExtras(commandIntent); // include the fg service type if any.
220         fgsIntent.setComponent(new ComponentName(targetPackage, FG_LOCATION_SERVICE_NAME));
221         int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE;
222         fgsIntent.putExtras(LocalForegroundService.newCommand(command));
223         try {
224             context.startForegroundService(fgsIntent);
225         } catch (ForegroundServiceStartNotAllowedException e) {
226             Log.d(TAG, "startForegroundService gets an "
227                     + "ForegroundServiceStartNotAllowedException", e);
228         }
229     }
230 
doStartForegroundServiceSticky(Context context, Intent commandIntent)231     private void doStartForegroundServiceSticky(Context context, Intent commandIntent) {
232         String targetPackage = getTargetPackage(commandIntent);
233         Intent fgsIntent = new Intent();
234         fgsIntent.putExtras(commandIntent);
235         fgsIntent.setComponent(new ComponentName(targetPackage, FG_STICKY_SERVICE_NAME));
236         int command = LocalForegroundService.COMMAND_START_FOREGROUND;
237         fgsIntent.putExtras(LocalForegroundService.newCommand(command));
238         try {
239             context.startForegroundService(fgsIntent);
240         } catch (ForegroundServiceStartNotAllowedException e) {
241             Log.d(TAG, "startForegroundService gets an "
242                     + "ForegroundServiceStartNotAllowedException", e);
243         }
244     }
245 
doStopService(Context context, Intent commandIntent, String serviceName)246     private void doStopService(Context context, Intent commandIntent,
247             String serviceName) {
248         String targetPackage = getTargetPackage(commandIntent);
249         Intent fgsIntent = new Intent();
250         fgsIntent.setComponent(new ComponentName(targetPackage, serviceName));
251         context.stopService(fgsIntent);
252     }
253 
doStartAlertService(Context context)254     private void doStartAlertService(Context context) {
255         Intent intent = new Intent(context, LocalAlertService.class);
256         intent.setAction(LocalAlertService.COMMAND_SHOW_ALERT);
257         context.startService(intent);
258     }
259 
doStopAlertService(Context context)260     private void doStopAlertService(Context context) {
261         Intent intent = new Intent(context, LocalAlertService.class);
262         intent.setAction(LocalAlertService.COMMAND_HIDE_ALERT);
263         context.startService(intent);
264     }
265 
doSelfInducedAnr(Context context)266     private void doSelfInducedAnr(Context context) {
267         ActivityManager am = context.getSystemService(ActivityManager.class);
268         am.appNotResponding("CTS - self induced");
269     }
270 
doStartActivity(Context context, Intent commandIntent)271     private void doStartActivity(Context context, Intent commandIntent) {
272         String targetPackage = getTargetPackage(commandIntent);
273         Intent activityIntent = new Intent(Intent.ACTION_MAIN);
274         sActivityIntent.put(targetPackage, activityIntent);
275         activityIntent.putExtras(commandIntent);
276         activityIntent.setComponent(new ComponentName(targetPackage, ACTIVITY_NAME));
277         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
278         context.startActivity(activityIntent);
279     }
280 
doStopActivity(Context context, Intent commandIntent)281     private void doStopActivity(Context context, Intent commandIntent) {
282         String targetPackage = getTargetPackage(commandIntent);
283         Intent activityIntent = sActivityIntent.remove(targetPackage);
284         activityIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
285         activityIntent.putExtra("finish", true);
286         context.startActivity(activityIntent);
287     }
288 
doCreateFgslPendingIntent(Context context, Intent commandIntent)289     private void doCreateFgslPendingIntent(Context context, Intent commandIntent) {
290         final String targetPackage = getTargetPackage(commandIntent);
291         final Intent intent = new Intent().setComponent(
292                 new ComponentName(targetPackage, FG_LOCATION_SERVICE_NAME));
293         int command = LocalForegroundServiceLocation.COMMAND_START_FOREGROUND_WITH_TYPE;
294         intent.putExtras(LocalForegroundService.newCommand(command));
295         final PendingIntent pendingIntent = PendingIntent.getForegroundService(context, 0,
296                 intent, PendingIntent.FLAG_IMMUTABLE);
297         sPendingIntent.put(targetPackage, pendingIntent);
298     }
299 
doSendFgslPendingIntent(Context context, Intent commandIntent)300     private void doSendFgslPendingIntent(Context context, Intent commandIntent) {
301         final String targetPackage = getTargetPackage(commandIntent);
302         try {
303             ((PendingIntent) sPendingIntent.remove(targetPackage)).send();
304         } catch (PendingIntent.CanceledException e) {
305             Log.e(TAG, "Caugtht exception:", e);
306         }
307     }
308 
doStartChildProcess(Context context, Intent intent)309     private void doStartChildProcess(Context context, Intent intent) {
310         final Bundle extras = intent.getExtras();
311         final IBinder callback = extras.getBinder(EXTRA_CALLBACK);
312         final String[] cmdline = extras.getStringArray(EXTRA_CHILD_CMDLINE);
313         final Parcel data = Parcel.obtain();
314         final Parcel reply = Parcel.obtain();
315 
316         try {
317             sChildProcess = Runtime.getRuntime().exec(cmdline);
318             if (sChildProcess != null) {
319                 Log.i(TAG, "Forked child: " + sChildProcess);
320                 callback.transact(RESULT_CHILD_PROCESS_STARTED, data, reply, 0);
321             } // else the remote will fail with timeout
322         } catch (Exception e) {
323             Log.e(TAG, "Unable to execute command", e);
324             sChildProcess = null;
325         } finally {
326             data.recycle();
327             reply.recycle();
328         }
329     }
330 
doStopChildProcess(Context context, Intent intent)331     private void doStopChildProcess(Context context, Intent intent) {
332         final Bundle extras = intent.getExtras();
333         final IBinder callback = extras.getBinder(EXTRA_CALLBACK);
334         final long timeout = extras.getLong(EXTRA_TIMEOUT);
335         waitForChildProcessGone(true, callback, RESULT_CHILD_PROCESS_STOPPED, timeout);
336     }
337 
doWaitForChildProcessGone(Context context, Intent intent)338     private void doWaitForChildProcessGone(Context context, Intent intent) {
339         final Bundle extras = intent.getExtras();
340         final IBinder callback = extras.getBinder(EXTRA_CALLBACK);
341         final long timeout = extras.getLong(EXTRA_TIMEOUT);
342         waitForChildProcessGone(false, callback, RESULT_CHILD_PROCESS_GONE, timeout);
343     }
344 
waitForChildProcessGone(final boolean destroy, final IBinder callback, final int transactionCode, final long timeout)345     private static synchronized void waitForChildProcessGone(final boolean destroy,
346             final IBinder callback, final int transactionCode, final long timeout) {
347         if (destroy) {
348             sChildProcess.destroy();
349         }
350         new Thread(() -> {
351             final Parcel data = Parcel.obtain();
352             final Parcel reply = Parcel.obtain();
353             try {
354                 if (sChildProcess != null && sChildProcess.isAlive()) {
355                     final boolean exit = sChildProcess.waitFor(timeout, TimeUnit.MILLISECONDS);
356                     if (exit) {
357                         Log.i(TAG, "Child process died: " + sChildProcess);
358                         callback.transact(transactionCode, data, reply, 0);
359                     } else {
360                         Log.w(TAG, "Child process is still alive: " + sChildProcess);
361                     }
362                 } else {
363                     callback.transact(transactionCode, data, reply, 0);
364                 }
365             } catch (Exception e) {
366                 Log.e(TAG, "Error", e);
367             } finally {
368                 data.recycle();
369                 reply.recycle();
370             }
371         }).start();
372     }
373 
getTargetPackage(Intent intent)374     private String getTargetPackage(Intent intent) {
375         return intent.getStringExtra(EXTRA_TARGET_PACKAGE);
376     }
377 
getFlags(Intent intent)378     private int getFlags(Intent intent) {
379         return intent.getIntExtra(EXTRA_FLAGS, 0);
380     }
381 
sendCommand(Context context, int command, String sourcePackage, String targetPackage, int flags, Bundle extras)382     public static void sendCommand(Context context, int command, String sourcePackage,
383             String targetPackage, int flags, Bundle extras) {
384         final Intent intent = makeIntent(command, sourcePackage, targetPackage, flags, extras);
385         Log.d(TAG, "Sending broadcast " + intent);
386         context.sendOrderedBroadcast(intent, null);
387     }
388 
sendCommandWithResultReceiver(Context context, int command, String sourcePackage, String targetPackage, int flags, Bundle extras, BroadcastReceiver resultReceiver)389     public static void sendCommandWithResultReceiver(Context context, int command,
390             String sourcePackage, String targetPackage, int flags, Bundle extras,
391             BroadcastReceiver resultReceiver) {
392         final Intent intent = makeIntent(command, sourcePackage, targetPackage, flags, extras);
393         Log.d(TAG, "Sending broadcast with result receiver " + intent);
394         context.sendOrderedBroadcast(intent, null, resultReceiver, null,
395                 Activity.RESULT_OK, null, null);
396     }
397 
sendCommandWithBroadcastOptions(Context context, int command, String sourcePackage, String targetPackage, int flags, Bundle extras, Bundle broadcastOptions)398     public static void sendCommandWithBroadcastOptions(Context context, int command,
399             String sourcePackage, String targetPackage, int flags, Bundle extras,
400             Bundle broadcastOptions) {
401         final Intent intent = makeIntent(command, sourcePackage, targetPackage, flags, extras);
402         Log.d(TAG, "Sending broadcast with BroadcastOptions " + intent);
403         context.sendOrderedBroadcast(intent, null, broadcastOptions, null, null, 0, null, null);
404     }
405 
makeIntent(int command, String sourcePackage, String targetPackage, int flags, Bundle extras)406     private static Intent makeIntent(int command, String sourcePackage,
407             String targetPackage, int flags, Bundle extras) {
408         Intent intent = new Intent();
409         if (command == COMMAND_BIND_SERVICE || command == COMMAND_START_FOREGROUND_SERVICE) {
410             intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
411         }
412         intent.setComponent(new ComponentName(sourcePackage, "android.app.stubs.CommandReceiver"));
413         intent.putExtra(EXTRA_COMMAND, command);
414         intent.putExtra(EXTRA_FLAGS, flags);
415         intent.putExtra(EXTRA_TARGET_PACKAGE, targetPackage);
416         if (extras != null) {
417             intent.putExtras(extras);
418         }
419         return intent;
420     }
421 
addServiceConnection(final String packageName)422     private ServiceConnection addServiceConnection(final String packageName) {
423         ServiceConnection connection = new ServiceConnection() {
424             @Override
425             public void onServiceConnected(ComponentName name, IBinder service) {
426             }
427 
428             @Override
429             public void onServiceDisconnected(ComponentName name) {
430             }
431         };
432         sServiceMap.put(packageName, connection);
433         return connection;
434     }
435 }
436