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 com.android.bedstead.testapp;
18 
19 import android.content.BroadcastReceiver;
20 import android.content.IntentFilter;
21 
22 import com.android.bedstead.nene.TestApis;
23 import com.android.bedstead.nene.exceptions.NeneException;
24 import com.android.bedstead.nene.packages.ProcessReference;
25 import com.android.bedstead.nene.users.UserReference;
26 
27 import com.google.android.enterprise.connectedapps.ConnectionListener;
28 import com.google.android.enterprise.connectedapps.CrossProfileConnector;
29 import com.google.android.enterprise.connectedapps.exceptions.UnavailableProfileException;
30 
31 import java.util.HashMap;
32 import java.util.Map;
33 import java.util.UUID;
34 
35 import javax.annotation.Nullable;
36 
37 /**
38  * A reference to a specific instance of a {@link TestApp} on a given user.
39  *
40  * <p>The user may not exist, or the test app may not be installed on the user.
41  */
42 public final class TestAppInstanceReference implements AutoCloseable, ConnectionListener {
43 
44     private static final TestApis sTestApis = new TestApis();
45 
46     private final TestApp mTestApp;
47     private final UserReference mUser;
48     private final CrossProfileConnector mConnector;
49     private final Map<IntentFilter, Long> mRegisteredBroadcastReceivers = new HashMap<>();
50     private boolean mKeepAliveManually = false;
51     private final ProfileTestAppController mTestAppController;
52     private final TestAppActivities mTestAppActivities;
53 
TestAppInstanceReference(TestApp testApp, UserReference user)54     TestAppInstanceReference(TestApp testApp, UserReference user) {
55         mTestApp = testApp;
56         mUser = user;
57         mConnector = CrossProfileConnector.builder(sTestApis.context().instrumentedContext())
58                 .setBinder(new TestAppBinder(this))
59                 .build();
60         mConnector.registerConnectionListener(this);
61         mTestAppController =
62                 ProfileTestAppController.create(mConnector);
63         mTestAppActivities = TestAppActivities.create(this);
64     }
65 
connector()66     CrossProfileConnector connector() {
67         return mConnector;
68     }
69 
70     /**
71      * Access activities on the test app.
72      */
activities()73     public TestAppActivities activities() {
74         return mTestAppActivities;
75     }
76 
77     /**
78      * The {@link TestApp} this instance refers to.
79      */
testApp()80     public TestApp testApp() {
81         return mTestApp;
82     }
83 
84     /**
85      * The {@link UserReference} this instance refers to.
86      */
user()87     public UserReference user() {
88         return mUser;
89     }
90 
91     /**
92      * Uninstall the {@link TestApp} from the user referenced by
93      * this {@link TestAppInstanceReference}.
94      */
uninstall()95     public void uninstall() {
96         mTestApp.uninstall(mUser);
97     }
98 
99     /**
100      * Register a {@link BroadcastReceiver} for a given {@link IntentFilter}.
101      *
102      * <p>A new {@link BroadcastReceiver} instance will be created for each {@link IntentFilter}.
103      *
104      * <p>Note that {@link IntentFilter} does not override {@code equals} and one broadcast receiver
105      * will be registered for each instance of {@link IntentFilter} regardless of the content of the
106      * {@link IntentFilter}.
107      *
108      * <p>As registered receivers are only active while the application is open, calling this method
109      * will have the same effect as calling {@link #keepAlive()}.
110      */
registerReceiver(IntentFilter intentFilter)111     public void registerReceiver(IntentFilter intentFilter) {
112         if (mRegisteredBroadcastReceivers.containsKey(intentFilter)) {
113             return;
114         }
115 
116         long receiverId = UUID.randomUUID().getMostSignificantBits();
117         registerReceiver(intentFilter, receiverId);
118         keepAlive(/* manualKeepAlive= */ false);
119     }
120 
registerReceiver(IntentFilter intentFilter, long receiverId)121     private void registerReceiver(IntentFilter intentFilter, long receiverId) {
122         try {
123             mConnector.connect();
124             mTestAppController.other().registerReceiver(receiverId, intentFilter);
125             mRegisteredBroadcastReceivers.put(intentFilter, receiverId);
126         } catch (UnavailableProfileException e) {
127             throw new IllegalStateException("Could not connect to test app", e);
128         } finally {
129             mConnector.stopManualConnectionManagement();
130         }
131     }
132 
133     /**
134      * Unregister the receiver
135      * @param intentFilter
136      */
unregisterReceiver(IntentFilter intentFilter)137     public TestAppInstanceReference unregisterReceiver(IntentFilter intentFilter) {
138         if (!mRegisteredBroadcastReceivers.containsKey(intentFilter)) {
139             return this;
140         }
141 
142         long receiverId = mRegisteredBroadcastReceivers.remove(intentFilter);
143 
144         try {
145             mConnector.connect();
146             mTestAppController.other().unregisterReceiver(receiverId);
147             mRegisteredBroadcastReceivers.put(intentFilter, receiverId);
148         } catch (UnavailableProfileException e) {
149             throw new IllegalStateException("Could not connect to test app", e);
150         } finally {
151             mConnector.stopManualConnectionManagement();
152         }
153 
154         if (mRegisteredBroadcastReceivers.isEmpty() && !mKeepAliveManually) {
155             stopKeepAlive();
156         }
157 
158         return this;
159     }
160 
161     /**
162      * Starts keeping the test app process alive.
163      *
164      * <p>This ensures that it will receive broadcasts using registered broadcast receivers.
165      *
166      * @see {@link #stopKeepAlive()}.
167      */
keepAlive()168     public TestAppInstanceReference keepAlive() {
169         keepAlive(/* manualKeepAlive=*/ true);
170         return this;
171     }
172 
173     /**
174      * Starts keep alive mode and marks it as manual so that it won't be automatically ended if
175      * the last broadcast receiver is unregistered.
176      */
keepAlive(boolean manualKeepAlive)177     private void keepAlive(boolean manualKeepAlive) {
178         mKeepAliveManually = manualKeepAlive;
179         try {
180             connector().connect();
181         } catch (UnavailableProfileException e) {
182             throw new IllegalStateException("Could not connect to test app. Is it installed?", e);
183         }
184     }
185 
186     /**
187      * Stops keeping the target app alive.
188      *
189      * <p>This will not kill the app immediately. To do that see {@link #stop()}.
190      */
stopKeepAlive()191     public TestAppInstanceReference stopKeepAlive() {
192         mKeepAliveManually = false;
193         connector().stopManualConnectionManagement();
194         return this;
195     }
196 
197     /**
198      * Immediately force stops the app.
199      *
200      * <p>This will also stop keeping the target app alive (see {@link #stopKeepAlive()}.
201      */
stop()202     public TestAppInstanceReference stop() {
203         stopKeepAlive();
204 
205         ProcessReference process = mTestApp.reference().runningProcess(mUser);
206         if (process != null) {
207             try {
208                 process.kill();
209             } catch (NeneException e) {
210                 throw new NeneException("Error killing process... process is " + process(), e);
211             }
212         }
213 
214         return this;
215     }
216 
217     /**
218      * Gets the {@link ProcessReference} of the app, if any.
219      */
220     @Nullable
process()221     public ProcessReference process() {
222         return mTestApp.reference().runningProcess(mUser);
223     }
224 
225     @Override
close()226     public void close() {
227         stopKeepAlive();
228         uninstall();
229     }
230 
231     @Override
connectionChanged()232     public void connectionChanged() {
233         if (mConnector.isConnected()) {
234             // re-register broadcast receivers when re-connected
235             for (Map.Entry<IntentFilter, Long> entry : mRegisteredBroadcastReceivers.entrySet()) {
236                 registerReceiver(entry.getKey(), entry.getValue());
237             }
238         }
239     }
240 }
241