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