1 /*
2  * Copyright (C) 2021 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.shared;
18 
19 import static android.app.PendingIntent.FLAG_IMMUTABLE;
20 
21 import static java.util.concurrent.TimeUnit.MILLISECONDS;
22 
23 import android.app.IActivityManager;
24 import android.app.Notification;
25 import android.app.NotificationChannel;
26 import android.app.NotificationManager;
27 import android.app.PendingIntent;
28 import android.app.Service;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.os.Build;
34 import android.os.IBinder;
35 import android.os.ParcelableException;
36 import android.os.RemoteException;
37 import android.os.ResultReceiver;
38 import android.os.ServiceManager;
39 import android.view.IWindowManager;
40 
41 import com.android.compatibility.common.util.PollingCheck;
42 
43 import java.util.concurrent.ExecutionException;
44 import java.util.concurrent.TimeoutException;
45 
46 /**
47  * This is a bound service used in conjunction with CloseSystemDialogsTest.
48  */
49 public class CloseSystemDialogsTestService extends Service {
50     private static final String TAG = "CloseSystemDialogsTestService";
51     private static final String NOTIFICATION_ACTION = TAG;
52     private static final String NOTIFICATION_CHANNEL_ID = "cts/" + TAG;
53     private static final Intent INTENT_ACSD = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
54 
55     private final ICloseSystemDialogsTestsService mBinder = new Binder();
56     private NotificationManager mNotificationManager;
57     private IWindowManager mWindowManager;
58     private IActivityManager mActivityManager;
59     private BroadcastReceiver mNotificationReceiver;
60 
61     @Override
onCreate()62     public void onCreate() {
63         super.onCreate();
64         mNotificationManager = getSystemService(NotificationManager.class);
65         mWindowManager = IWindowManager.Stub.asInterface(
66                 ServiceManager.getService(Context.WINDOW_SERVICE));
67         mActivityManager = IActivityManager.Stub.asInterface(
68                 ServiceManager.getService(Context.ACTIVITY_SERVICE));
69     }
70 
71     @Override
onBind(Intent intent)72     public IBinder onBind(Intent intent) {
73         return mBinder.asBinder();
74     }
75 
76     @Override
onDestroy()77     public void onDestroy() {
78         super.onDestroy();
79         if (mNotificationReceiver != null) {
80             unregisterReceiver(mNotificationReceiver);
81         }
82     }
83 
84     private class Binder extends ICloseSystemDialogsTestsService.Stub {
85         private final Context mContext = CloseSystemDialogsTestService.this;
86 
87         /** Checks that it can call @hide methods. */
88         @Override
waitUntilReady(long timeoutMs)89         public boolean waitUntilReady(long timeoutMs) {
90             try {
91                 PollingCheck.check("Can't call @hide methods", timeoutMs, () -> {
92                     try {
93                         // Any method suffices since IWindowManager is @hide
94                         mWindowManager.isRotationFrozen();
95                         return true;
96                     } catch (NoSuchMethodError e) {
97                         return false;
98                     }
99                 });
100                 return true;
101             } catch (AssertionError e) {
102                 return false;
103             } catch (Exception e) {
104                 throw new RuntimeException(e);
105             }
106         }
107 
108         @Override
sendCloseSystemDialogsBroadcast()109         public void sendCloseSystemDialogsBroadcast() {
110             mContext.sendBroadcast(INTENT_ACSD);
111         }
112 
113         @Override
postNotification(int notificationId, ResultReceiver receiver, boolean usePendingIntent)114         public void postNotification(int notificationId, ResultReceiver receiver,
115                 boolean usePendingIntent) {
116             mNotificationReceiver = new BroadcastReceiver() {
117                 @Override
118                 public void onReceive(Context context, Intent intent) {
119                     try {
120                         if (usePendingIntent) {
121                             PendingIntent.getBroadcast(mContext, /* requestCode */ 0, INTENT_ACSD,
122                                     FLAG_IMMUTABLE).send();
123                         } else {
124                             mContext.sendBroadcast(INTENT_ACSD);
125                         }
126                         receiver.send(RESULT_OK, null);
127                     } catch (SecurityException e) {
128                         receiver.send(RESULT_SECURITY_EXCEPTION, null);
129                     } catch (PendingIntent.CanceledException e) {
130                         receiver.send(RESULT_ERROR, null);
131                     }
132                 }
133             };
134             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
135                 mContext.registerReceiver(mNotificationReceiver,
136                         new IntentFilter(NOTIFICATION_ACTION), Context.RECEIVER_EXPORTED_UNAUDITED);
137             } else {
138                 mContext.registerReceiver(mNotificationReceiver,
139                         new IntentFilter(NOTIFICATION_ACTION));
140             }
141             Intent intent = new Intent(NOTIFICATION_ACTION);
142             intent.setPackage(mContext.getPackageName());
143             CloseSystemDialogsTestService.this.notify(
144                     notificationId,
145                     PendingIntent.getBroadcast(mContext, 0, intent, FLAG_IMMUTABLE));
146         }
147 
148         @Override
closeSystemDialogsViaWindowManager(String reason)149         public void closeSystemDialogsViaWindowManager(String reason) throws RemoteException {
150             mWindowManager.closeSystemDialogs(reason);
151         }
152 
153         @Override
closeSystemDialogsViaActivityManager(String reason)154         public void closeSystemDialogsViaActivityManager(String reason) throws RemoteException {
155             mActivityManager.closeSystemDialogs(reason);
156         }
157 
158         @Override
waitForAccessibilityServiceWindow(long timeoutMs)159         public boolean waitForAccessibilityServiceWindow(long timeoutMs) throws RemoteException {
160             final AppAccessibilityService service;
161             try {
162                 service = AppAccessibilityService.getConnected().get(timeoutMs, MILLISECONDS);
163             } catch (TimeoutException e) {
164                 return false;
165             } catch (ExecutionException | InterruptedException e) {
166                 throw new ParcelableException(e);
167             }
168             return service.waitWindowAdded(timeoutMs);
169         }
170     }
171 
notify(int notificationId, PendingIntent intent)172     private void notify(int notificationId, PendingIntent intent) {
173         Notification notification =
174                 new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
175                         .setSmallIcon(android.R.drawable.ic_info)
176                         .setContentIntent(intent)
177                         .build();
178         NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
179                 NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT);
180         mNotificationManager.createNotificationChannel(notificationChannel);
181         mNotificationManager.notify(notificationId, notification);
182     }
183 }
184