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