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 * <p> 35 * 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 79 * by 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 */ 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 = Integer.valueOf(mClientProxy.getId()); 93 } catch (RemoteException e) { 94 throw e.rethrowFromSystemServer(); 95 } 96 } 97 98 /** 99 * Returns the hub that this client is attached to. 100 * 101 * @return the ContextHubInfo of the attached hub 102 */ 103 @NonNull getAttachedHub()104 public ContextHubInfo getAttachedHub() { 105 return mAttachedHub; 106 } 107 108 /** 109 * Returns the system-wide unique identifier for this ContextHubClient. 110 * 111 * This value can be used as an identifier for the messaging channel between a 112 * ContextHubClient and the Context Hub. This may be used as a routing mechanism 113 * between various ContextHubClient objects within an application. 114 * <p> 115 * The value returned by this method will remain the same if it is associated with 116 * the same client reference at the ContextHubService (for instance, the ID of a 117 * PendingIntent ContextHubClient will remain the same even if the local object 118 * has been regenerated with the equivalent PendingIntent). If the ContextHubClient 119 * is newly generated (e.g. any regeneration of a callback client, or generation 120 * of a non-equal PendingIntent client), the ID will not be the same. 121 * 122 * @return The ID of this ContextHubClient, in the range [0, 65535]. 123 */ 124 @IntRange(from = 0, to = 65535) getId()125 public int getId() { 126 if (mId == null) { 127 throw new IllegalStateException("ID was not set"); 128 } 129 return (0x0000FFFF & mId); 130 } 131 132 /** 133 * Closes the connection for this client and the Context Hub Service. 134 * 135 * When this function is invoked, the messaging associated with this client is invalidated. 136 * All futures messages targeted for this client are dropped at the service, and the 137 * ContextHubClient is unregistered from the service. 138 * <p> 139 * If this object has a PendingIntent, i.e. the object was generated via 140 * {@link ContextHubManager.createClient(PendingIntent, ContextHubInfo, long)}, then the 141 * Intent events corresponding to the PendingIntent will no longer be triggered. 142 */ close()143 public void close() { 144 if (!mIsClosed.getAndSet(true)) { 145 if (mCloseGuard != null) { 146 mCloseGuard.close(); 147 } 148 try { 149 mClientProxy.close(); 150 } catch (RemoteException e) { 151 throw e.rethrowFromSystemServer(); 152 } 153 } 154 } 155 156 /** 157 * Sends a message to a nanoapp through the Context Hub Service. 158 * 159 * This function returns RESULT_SUCCESS if the message has reached the HAL, but 160 * does not guarantee delivery of the message to the target nanoapp. 161 * <p> 162 * Before sending the first message to your nanoapp, it's recommended that the following 163 * operations should be performed: 164 * 1) Invoke {@link ContextHubManager#queryNanoApps(ContextHubInfo)} to verify the nanoapp is 165 * present. 166 * 2) Validate that you have the permissions to communicate with the nanoapp by looking at 167 * {@link NanoAppState#getNanoAppPermissions}. 168 * 3) If you don't have permissions, send an idempotent message to the nanoapp ensuring any 169 * work your app previously may have asked it to do is stopped. This is useful if your app 170 * restarts due to permission changes and no longer has the permissions when it is started 171 * again. 172 * 4) If you have valid permissions, send a message to your nanoapp to resubscribe so that it's 173 * aware you have restarted or so you can initially subscribe if this is the first time you 174 * have sent it a message. 175 * 176 * @param message the message object to send 177 * 178 * @return the result of sending the message defined as in ContextHubTransaction.Result 179 * 180 * @throws NullPointerException if NanoAppMessage is null 181 * @throws SecurityException if this client doesn't have permissions to send a message to the 182 * nanoapp. 183 * 184 * @see NanoAppMessage 185 * @see ContextHubTransaction.Result 186 */ 187 @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) 188 @ContextHubTransaction.Result sendMessageToNanoApp(@onNull NanoAppMessage message)189 public int sendMessageToNanoApp(@NonNull NanoAppMessage message) { 190 Objects.requireNonNull(message, "NanoAppMessage cannot be null"); 191 192 int maxPayloadBytes = mAttachedHub.getMaxPacketLengthBytes(); 193 byte[] payload = message.getMessageBody(); 194 if (payload != null && payload.length > maxPayloadBytes) { 195 Log.e(TAG, "Message (" + payload.length + " bytes) exceeds max payload length (" 196 + maxPayloadBytes + " bytes)"); 197 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS; 198 } 199 200 try { 201 return mClientProxy.sendMessageToNanoApp(message); 202 } catch (RemoteException e) { 203 throw e.rethrowFromSystemServer(); 204 } 205 } 206 207 @Override finalize()208 protected void finalize() throws Throwable { 209 try { 210 if (mCloseGuard != null) { 211 mCloseGuard.warnIfOpen(); 212 } 213 if (!mPersistent) { 214 close(); 215 } 216 } finally { 217 super.finalize(); 218 } 219 } 220 } 221