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