1 /*
2  * Copyright 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 package android.hardware.location;
17 
18 import android.annotation.FlaggedApi;
19 import android.annotation.IntRange;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SystemApi;
24 import android.app.PendingIntent;
25 import android.chre.flags.Flags;
26 import android.os.RemoteException;
27 import android.util.Log;
28 
29 import dalvik.system.CloseGuard;
30 
31 import java.io.Closeable;
32 import java.util.Objects;
33 import java.util.concurrent.atomic.AtomicBoolean;
34 
35 /**
36  * A class describing a client of the Context Hub Service.
37  *
38  * <p>Clients can send messages to nanoapps at a Context Hub through this object. The APIs supported
39  * by this object are thread-safe and can be used without external synchronization.
40  *
41  * @hide
42  */
43 @SystemApi
44 public class ContextHubClient implements Closeable {
45     private static final String TAG = "ContextHubClient";
46 
47     /*
48      * The proxy to the client interface at the service.
49      */
50     private IContextHubClient mClientProxy = null;
51 
52     /*
53      * The Context Hub that this client is attached to.
54      */
55     private final ContextHubInfo mAttachedHub;
56 
57     private final CloseGuard mCloseGuard;
58 
59     private final AtomicBoolean mIsClosed = new AtomicBoolean(false);
60 
61     /*
62      * True if this is a persistent client (i.e. does not have to close the connection when the
63      * resource is freed from the system).
64      */
65     private final boolean mPersistent;
66 
67     private Integer mId = null;
68 
ContextHubClient(ContextHubInfo hubInfo, boolean persistent)69     /* package */ ContextHubClient(ContextHubInfo hubInfo, boolean persistent) {
70         mAttachedHub = hubInfo;
71         mPersistent = persistent;
72         if (mPersistent) {
73             mCloseGuard = null;
74         } else {
75             mCloseGuard = CloseGuard.get();
76             mCloseGuard.open("ContextHubClient.close");
77         }
78     }
79 
80     /**
81      * Sets the proxy interface of the client at the service. This method should always be called by
82      * the ContextHubManager after the client is registered at the service, and should only be
83      * called once.
84      *
85      * @param clientProxy the proxy of the client at the service
86      */
setClientProxy(IContextHubClient clientProxy)87     /* package */ synchronized void setClientProxy(IContextHubClient clientProxy) {
88         Objects.requireNonNull(clientProxy, "IContextHubClient cannot be null");
89         if (mClientProxy != null) {
90             throw new IllegalStateException("Cannot change client proxy multiple times");
91         }
92 
93         mClientProxy = clientProxy;
94         try {
95             mId = mClientProxy.getId();
96         } catch (RemoteException e) {
97             throw e.rethrowFromSystemServer();
98         }
99         this.notifyAll();
100     }
101 
102     /**
103      * Returns the hub that this client is attached to.
104      *
105      * @return the ContextHubInfo of the attached hub
106      */
107     @NonNull
getAttachedHub()108     public ContextHubInfo getAttachedHub() {
109         return mAttachedHub;
110     }
111 
112     /**
113      * Returns the system-wide unique identifier for this ContextHubClient.
114      *
115      * <p>This value can be used as an identifier for the messaging channel between a
116      * ContextHubClient and the Context Hub. This may be used as a routing mechanism between various
117      * ContextHubClient objects within an application.
118      *
119      * <p>The value returned by this method will remain the same if it is associated with the same
120      * client reference at the ContextHubService (for instance, the ID of a PendingIntent
121      * ContextHubClient will remain the same even if the local object has been regenerated with the
122      * equivalent PendingIntent). If the ContextHubClient is newly generated (e.g. any regeneration
123      * of a callback client, or generation of a non-equal PendingIntent client), the ID will not be
124      * the same.
125      *
126      * @return The ID of this ContextHubClient, in the range [0, 65535].
127      */
128     @IntRange(from = 0, to = 65535)
getId()129     public int getId() {
130         if (mId == null) {
131             throw new IllegalStateException("ID was not set");
132         }
133         return (0x0000FFFF & mId);
134     }
135 
136     /**
137      * Closes the connection for this client and the Context Hub Service.
138      *
139      * <p>When this function is invoked, the messaging associated with this client is invalidated.
140      * All futures messages targeted for this client are dropped at the service, and the
141      * ContextHubClient is unregistered from the service.
142      *
143      * <p>If this object has a PendingIntent, i.e. the object was generated via {@link
144      * ContextHubManager#createClient(ContextHubInfo, PendingIntent, long)}, then the Intent events
145      * corresponding to the PendingIntent will no longer be triggered.
146      */
close()147     public void close() {
148         if (!mIsClosed.getAndSet(true)) {
149             if (mCloseGuard != null) {
150                 mCloseGuard.close();
151             }
152             try {
153                 mClientProxy.close();
154             } catch (RemoteException e) {
155                 throw e.rethrowFromSystemServer();
156             }
157         }
158     }
159 
160     /**
161      * Sends a message to a nanoapp through the Context Hub Service.
162      *
163      * This function returns RESULT_SUCCESS if the message has reached the HAL, but
164      * does not guarantee delivery of the message to the target nanoapp.
165      * <p>
166      * Before sending the first message to your nanoapp, it's recommended that the following
167      * operations should be performed:
168      * 1) Invoke {@link ContextHubManager#queryNanoApps(ContextHubInfo)} to verify the nanoapp is
169      *    present.
170      * 2) Validate that you have the permissions to communicate with the nanoapp by looking at
171      *    {@link NanoAppState#getNanoAppPermissions}.
172      * 3) If you don't have permissions, send an idempotent message to the nanoapp ensuring any
173      *    work your app previously may have asked it to do is stopped. This is useful if your app
174      *    restarts due to permission changes and no longer has the permissions when it is started
175      *    again.
176      * 4) If you have valid permissions, send a message to your nanoapp to resubscribe so that it's
177      *    aware you have restarted or so you can initially subscribe if this is the first time you
178      *    have sent it a message.
179      *
180      * @param message the message object to send
181      * @return the result of sending the message defined as in ContextHubTransaction.Result
182      * @throws NullPointerException if NanoAppMessage is null
183      * @throws SecurityException if this client doesn't have permissions to send a message to the
184      *     nanoapp.
185      * @see NanoAppMessage
186      * @see ContextHubTransaction.Result
187      */
188     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
189     @ContextHubTransaction.Result
sendMessageToNanoApp(@onNull NanoAppMessage message)190     public int sendMessageToNanoApp(@NonNull NanoAppMessage message) {
191         return doSendMessageToNanoApp(message, null);
192     }
193 
194     /**
195      * Sends a reliable message to a nanoapp.
196      *
197      * This method is similar to {@link ContextHubClient#sendMessageToNanoApp} with the
198      * difference that it expects the message to be acknowledged by CHRE.
199      *
200      * The transaction succeeds after we received an ACK from CHRE without error.
201      * In all other cases the transaction will fail.
202      */
203     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
204     @NonNull
205     @FlaggedApi(Flags.FLAG_RELIABLE_MESSAGE)
sendReliableMessageToNanoApp( @onNull NanoAppMessage message)206     public ContextHubTransaction<Void> sendReliableMessageToNanoApp(
207             @NonNull NanoAppMessage message) {
208         ContextHubTransaction<Void> transaction =
209                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_RELIABLE_MESSAGE);
210 
211         if (!Flags.reliableMessageImplementation() ||
212             !mAttachedHub.supportsReliableMessages() ||
213             message.isBroadcastMessage()) {
214             transaction.setResponse(new ContextHubTransaction.Response<Void>(
215                     ContextHubTransaction.RESULT_FAILED_NOT_SUPPORTED, null));
216             return transaction;
217         }
218 
219         IContextHubTransactionCallback callback =
220                 ContextHubTransactionHelper.createTransactionCallback(transaction);
221 
222         @ContextHubTransaction.Result int result = doSendMessageToNanoApp(message, callback);
223         if (result != ContextHubTransaction.RESULT_SUCCESS) {
224             transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
225         }
226 
227         return transaction;
228     }
229 
230     /**
231      * Sends a message to a nanoapp.
232      *
233      * @param message The message to send.
234      * @param transactionCallback The callback to use when the message is reliable. null for regular
235      *         messages.
236      * @return A {@link ContextHubTransaction.Result} error code.
237      */
238     @ContextHubTransaction.Result
doSendMessageToNanoApp(@onNull NanoAppMessage message, @Nullable IContextHubTransactionCallback transactionCallback)239     private int doSendMessageToNanoApp(@NonNull NanoAppMessage message,
240             @Nullable IContextHubTransactionCallback transactionCallback) {
241         Objects.requireNonNull(message, "NanoAppMessage cannot be null");
242 
243         int maxPayloadBytes = mAttachedHub.getMaxPacketLengthBytes();
244 
245         byte[] payload = message.getMessageBody();
246         if (payload != null && payload.length > maxPayloadBytes) {
247             Log.e(TAG,
248                     "Message (%d bytes) exceeds max payload length (%d bytes)".formatted(
249                             payload.length, maxPayloadBytes));
250             return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
251         }
252 
253         try {
254             if (transactionCallback == null) {
255                 return mClientProxy.sendMessageToNanoApp(message);
256             }
257             return mClientProxy.sendReliableMessageToNanoApp(message, transactionCallback);
258         } catch (RemoteException e) {
259             throw e.rethrowFromSystemServer();
260         }
261     }
262 
263     @Override
finalize()264     protected void finalize() throws Throwable {
265         try {
266             if (mCloseGuard != null) {
267                 mCloseGuard.warnIfOpen();
268             }
269             if (!mPersistent) {
270                 close();
271             }
272         } finally {
273             super.finalize();
274         }
275     }
276 
277     /** @hide */
callbackFinished()278     public synchronized void callbackFinished() {
279         try {
280             waitForClientProxy();
281             mClientProxy.callbackFinished();
282         } catch (RemoteException e) {
283             throw e.rethrowFromSystemServer();
284         }
285     }
286 
287     /** @hide */
reliableMessageCallbackFinished(int messageSequenceNumber, byte errorCode)288     public synchronized void reliableMessageCallbackFinished(int messageSequenceNumber,
289             byte errorCode) {
290         try {
291             waitForClientProxy();
292             mClientProxy.reliableMessageCallbackFinished(messageSequenceNumber, errorCode);
293         } catch (RemoteException e) {
294             throw e.rethrowFromSystemServer();
295         }
296     }
297 
298     /** @hide */
waitForClientProxy()299     private void waitForClientProxy() {
300         while (mClientProxy == null) {
301             try {
302                 this.wait();
303             } catch (InterruptedException e) {
304                 Thread.currentThread().interrupt();
305             }
306         }
307     }
308 }
309