1 /*
2  * Copyright (C) 2020 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 com.android.server.location.contexthub;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 import android.hardware.contexthub.HostEndpointInfo;
21 import android.hardware.contexthub.NanSessionRequest;
22 import android.hardware.contexthub.V1_0.ContextHub;
23 import android.hardware.contexthub.V1_0.ContextHubMsg;
24 import android.hardware.contexthub.V1_0.TransactionResult;
25 import android.hardware.contexthub.V1_1.Setting;
26 import android.hardware.contexthub.V1_1.SettingValue;
27 import android.hardware.contexthub.V1_2.HubAppInfo;
28 import android.hardware.contexthub.V1_2.IContexthubCallback;
29 import android.hardware.location.ContextHubInfo;
30 import android.hardware.location.ContextHubTransaction;
31 import android.hardware.location.NanoAppBinary;
32 import android.hardware.location.NanoAppMessage;
33 import android.hardware.location.NanoAppState;
34 import android.os.Handler;
35 import android.os.HandlerThread;
36 import android.os.IBinder;
37 import android.os.Process;
38 import android.os.RemoteException;
39 import android.os.ServiceManager;
40 import android.os.ServiceSpecificException;
41 import android.util.Log;
42 import android.util.Pair;
43 
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.Collections;
47 import java.util.HashMap;
48 import java.util.HashSet;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.NoSuchElementException;
52 import java.util.Set;
53 
54 /**
55  * @hide
56  */
57 public abstract class IContextHubWrapper {
58     private static final String TAG = "IContextHubWrapper";
59 
60     /**
61      * The callback interface to use in registerCallback.
62      */
63     public interface ICallback {
64         /**
65          * @param transactionId The ID of the transaction that completed.
66          * @param success       true if the transaction succeeded.
67          */
handleTransactionResult(int transactionId, boolean success)68         void handleTransactionResult(int transactionId, boolean success);
69 
70         /**
71          * @param eventType The Context Hub event type defined by ContextHubService
72          *                  .CONTEXT_HUB_EVENT_*.
73          */
handleContextHubEvent(int eventType)74         void handleContextHubEvent(int eventType);
75 
76         /**
77          * @param nanoappId The ID of the nanoapp that aborted.
78          * @param abortCode The nanoapp-defined abort code.
79          */
handleNanoappAbort(long nanoappId, int abortCode)80         void handleNanoappAbort(long nanoappId, int abortCode);
81 
82         /**
83          * @param nanoappStateList The list of loaded nanoapps on the Context Hub.
84          */
handleNanoappInfo(List<NanoAppState> nanoappStateList)85         void handleNanoappInfo(List<NanoAppState> nanoappStateList);
86 
87         /**
88          * Handles a message from a nanoapp to a ContextHubClient.
89          *
90          * @param hostEndpointId     The host endpoint ID of the recipient.
91          * @param message            The message from the nanoapp.
92          * @param nanoappPermissions The list of permissions held by the nanoapp.
93          * @param messagePermissions The list of permissions required to receive the message.
94          */
handleNanoappMessage(short hostEndpointId, NanoAppMessage message, List<String> nanoappPermissions, List<String> messagePermissions)95         void handleNanoappMessage(short hostEndpointId, NanoAppMessage message,
96                 List<String> nanoappPermissions, List<String> messagePermissions);
97 
98         /**
99          * Handles a restart of the service
100          */
handleServiceRestart()101         void handleServiceRestart();
102     }
103 
104     /**
105      * @return the IContextHubWrapper interface
106      */
getContextHubWrapper()107     public static IContextHubWrapper getContextHubWrapper() {
108         IContextHubWrapper wrapper = maybeConnectToAidl();
109         if (wrapper == null) {
110             wrapper = maybeConnectTo1_2();
111         }
112         if (wrapper == null) {
113             wrapper = maybeConnectTo1_1();
114         }
115         if (wrapper == null) {
116             wrapper = maybeConnectTo1_0();
117         }
118 
119         return wrapper;
120     }
121 
122     /**
123      * Attempts to connect to the Contexthub HAL 1.0 service, if it exists.
124      *
125      * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
126      */
127     @Nullable
maybeConnectTo1_0()128     public static IContextHubWrapper maybeConnectTo1_0() {
129         android.hardware.contexthub.V1_0.IContexthub proxy = null;
130         try {
131             proxy = android.hardware.contexthub.V1_0.IContexthub.getService(true /* retry */);
132         } catch (RemoteException e) {
133             Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e);
134         } catch (NoSuchElementException e) {
135             Log.i(TAG, "Context Hub HAL service not found");
136         }
137 
138         return (proxy == null) ? null : new ContextHubWrapperV1_0(proxy);
139     }
140 
141     /**
142      * Attempts to connect to the Contexthub HAL 1.1 service, if it exists.
143      *
144      * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
145      */
146     @Nullable
maybeConnectTo1_1()147     public static IContextHubWrapper maybeConnectTo1_1() {
148         android.hardware.contexthub.V1_1.IContexthub proxy = null;
149         try {
150             proxy = android.hardware.contexthub.V1_1.IContexthub.getService(true /* retry */);
151         } catch (RemoteException e) {
152             Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e);
153         } catch (NoSuchElementException e) {
154             Log.i(TAG, "Context Hub HAL service not found");
155         }
156 
157         return (proxy == null) ? null : new ContextHubWrapperV1_1(proxy);
158     }
159 
160     /**
161      * Attempts to connect to the Contexthub HAL 1.2 service, if it exists.
162      *
163      * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
164      */
165     @Nullable
maybeConnectTo1_2()166     public static IContextHubWrapper maybeConnectTo1_2() {
167         android.hardware.contexthub.V1_2.IContexthub proxy = null;
168         try {
169             proxy = android.hardware.contexthub.V1_2.IContexthub.getService(true /* retry */);
170         } catch (RemoteException e) {
171             Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e);
172         } catch (NoSuchElementException e) {
173             Log.i(TAG, "Context Hub HAL service not found");
174         }
175 
176         return (proxy == null) ? null : new ContextHubWrapperV1_2(proxy);
177     }
178 
179     /**
180      * Attempts to connect to the AIDL HAL and returns the proxy IContextHub.
181      */
maybeConnectToAidlGetProxy()182     public static android.hardware.contexthub.IContextHub maybeConnectToAidlGetProxy() {
183         android.hardware.contexthub.IContextHub proxy = null;
184         final String aidlServiceName =
185                 android.hardware.contexthub.IContextHub.class.getCanonicalName() + "/default";
186         if (ServiceManager.isDeclared(aidlServiceName)) {
187             proxy = android.hardware.contexthub.IContextHub.Stub.asInterface(
188                     ServiceManager.waitForService(aidlServiceName));
189             if (proxy == null) {
190                 Log.e(TAG, "Context Hub AIDL service was declared but was not found");
191             }
192         } else {
193             Log.d(TAG, "Context Hub AIDL service is not declared");
194         }
195         return proxy;
196     }
197 
198     /**
199      * Attempts to connect to the Contexthub HAL AIDL service, if it exists.
200      *
201      * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
202      */
203     @Nullable
maybeConnectToAidl()204     public static IContextHubWrapper maybeConnectToAidl() {
205         android.hardware.contexthub.IContextHub proxy = maybeConnectToAidlGetProxy();
206         return proxy == null ? null : new ContextHubWrapperAidl(proxy);
207     }
208 
209     /**
210      * Calls the appropriate getHubs function depending on the HAL version.
211      */
getHubs()212     public abstract Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException;
213 
214     /**
215      * @return True if this version of the Contexthub HAL supports Location setting notifications.
216      */
supportsLocationSettingNotifications()217     public abstract boolean supportsLocationSettingNotifications();
218 
219     /**
220      * Notifies the Contexthub implementation of a user Location setting change.
221      *
222      * @param enabled True if the Location setting has been enabled.
223      */
onLocationSettingChanged(boolean enabled)224     public abstract void onLocationSettingChanged(boolean enabled);
225 
226     /**
227      * @return True if this version of the Contexthub HAL supports WiFi availability setting
228      * notifications.
229      */
supportsWifiSettingNotifications()230     public abstract boolean supportsWifiSettingNotifications();
231 
232     /**
233      * Notifies the Contexthub implementation of a user WiFi availability setting change.
234      *
235      * @param enabled true if the WiFi availability setting has been enabled.
236      */
onWifiSettingChanged(boolean enabled)237     public abstract void onWifiSettingChanged(boolean enabled);
238 
239     /**
240      * Notifies the Contexthub implementation of a user WiFi main setting change.
241      *
242      * @param enabled true if the WiFi main setting has been enabled.
243      */
onWifiMainSettingChanged(boolean enabled)244     public abstract void onWifiMainSettingChanged(boolean enabled);
245 
246     /**
247      * Notifies the Contexthub implementation of a user WiFi scanning setting change.
248      *
249      * @param enabled true if the WiFi scanning setting has been enabled.
250      */
onWifiScanningSettingChanged(boolean enabled)251     public abstract void onWifiScanningSettingChanged(boolean enabled);
252 
253     /**
254      * @return True if this version of the Contexthub HAL supports airplane mode setting
255      * notifications.
256      */
supportsAirplaneModeSettingNotifications()257     public abstract boolean supportsAirplaneModeSettingNotifications();
258 
259     /**
260      * Notifies the Contexthub implementation of an airplane mode setting change.
261      *
262      * @param enabled true if the airplane mode setting has been enabled.
263      */
onAirplaneModeSettingChanged(boolean enabled)264     public abstract void onAirplaneModeSettingChanged(boolean enabled);
265 
266     /**
267      * @return True if this version of the Contexthub HAL supports microphone setting
268      * notifications.
269      */
supportsMicrophoneSettingNotifications()270     public abstract boolean supportsMicrophoneSettingNotifications();
271 
272     /**
273      * Notifies the Contexthub implementation of a microphone setting change.
274      */
onMicrophoneSettingChanged(boolean enabled)275     public abstract void onMicrophoneSettingChanged(boolean enabled);
276 
277     /**
278      * @return True if this version of the Contexthub HAL supports BT availability setting
279      * notifications.
280      */
supportsBtSettingNotifications()281     public abstract boolean supportsBtSettingNotifications();
282 
283     /**
284      * Notifies the Contexthub implementation of a BT main setting change.
285      */
onBtMainSettingChanged(boolean enabled)286     public abstract void onBtMainSettingChanged(boolean enabled);
287 
288     /**
289      * Notifies the Contexthub implementation of a BT scanning setting change.
290      */
onBtScanningSettingChanged(boolean enabled)291     public abstract void onBtScanningSettingChanged(boolean enabled);
292 
293     /**
294      * Invoked whenever a host client connects with the framework.
295      *
296      * @param info The host endpoint info.
297      */
onHostEndpointConnected(HostEndpointInfo info)298     public void onHostEndpointConnected(HostEndpointInfo info) {}
299 
300     /**
301      * Invoked whenever a host client disconnects from the framework.
302      *
303      * @param hostEndpointId The ID of the host endpoint that disconnected.
304      */
onHostEndpointDisconnected(short hostEndpointId)305     public void onHostEndpointDisconnected(short hostEndpointId) {}
306 
307     /**
308      * Sends a message to the Context Hub.
309      *
310      * @param hostEndpointId The host endpoint ID of the sender.
311      * @param contextHubId   The ID of the Context Hub to send the message to.
312      * @param message        The message to send.
313      * @return the result of the message sending.
314      */
315     @ContextHubTransaction.Result
sendMessageToContextHub( short hostEndpointId, int contextHubId, NanoAppMessage message)316     public abstract int sendMessageToContextHub(
317             short hostEndpointId, int contextHubId, NanoAppMessage message)
318             throws RemoteException;
319 
320     /**
321      * Loads a nanoapp on the Context Hub.
322      *
323      * @param contextHubId  The ID of the Context Hub to load the nanoapp to.
324      * @param binary        The nanoapp binary to load.
325      * @param transactionId The transaction ID of this load.
326      * @return the result of this load transaction.
327      */
328     @ContextHubTransaction.Result
loadNanoapp(int contextHubId, NanoAppBinary binary, int transactionId)329     public abstract int loadNanoapp(int contextHubId, NanoAppBinary binary,
330             int transactionId) throws RemoteException;
331 
332     /**
333      * Unloads a nanoapp on the Context Hub. Semantics are similar to loadNanoapp().
334      */
335     @ContextHubTransaction.Result
unloadNanoapp(int contextHubId, long nanoappId, int transactionId)336     public abstract int unloadNanoapp(int contextHubId, long nanoappId,
337             int transactionId) throws RemoteException;
338 
339     /**
340      * Enables a nanoapp on the Context Hub. Semantics are similar to loadNanoapp().
341      */
342     @ContextHubTransaction.Result
enableNanoapp(int contextHubId, long nanoappId, int transactionId)343     public abstract int enableNanoapp(int contextHubId, long nanoappId,
344             int transactionId) throws RemoteException;
345 
346     /**
347      * Disables a nanoapp on the Context Hub. Semantics are similar to loadNanoapp().
348      */
349     @ContextHubTransaction.Result
disableNanoapp(int contextHubId, long nanoappId, int transactionId)350     public abstract int disableNanoapp(int contextHubId, long nanoappId,
351             int transactionId) throws RemoteException;
352 
353     /**
354      * Queries a list of nanoapp from the Context hub.
355      *
356      * @param contextHubId The ID of the Context Hub to query.
357      * @return the result of this query transaction.
358      */
359     @ContextHubTransaction.Result
queryNanoapps(int contextHubId)360     public abstract int queryNanoapps(int contextHubId) throws RemoteException;
361 
362     /**
363      * Provides the list of preloaded nanoapp IDs on the system. The output of this API must
364      * not change.
365      *
366      * @param contextHubId  The context Hub ID.
367      *
368      * @return The list of preloaded nanoapp IDs.
369      */
getPreloadedNanoappIds(int contextHubId)370     public abstract long[] getPreloadedNanoappIds(int contextHubId);
371 
372     /**
373      * Registers a callback with the Context Hub.
374      *
375      * @param contextHubId The ID of the Context Hub to register the callback with.
376      * @param callback     The callback to register.
377      */
registerCallback(int contextHubId, @NonNull ICallback callback)378     public abstract void registerCallback(int contextHubId, @NonNull ICallback callback)
379             throws RemoteException;
380 
381     /**
382      * Registers an existing callback with the Context Hub.
383      *
384      * @param contextHubId The ID of the Context Hub to register the callback with.
385      */
registerExistingCallback(int contextHubId)386     public abstract void registerExistingCallback(int contextHubId) throws RemoteException;
387 
388     /**
389      * Puts the context hub in and out of test mode. Test mode is a clean state
390      * where tests can be executed in the same environment. If enable is true,
391      * this will enable test mode by unloading all nanoapps. If enable is false,
392      * this will disable test mode and reverse the actions of enabling test mode
393      * by loading all preloaded nanoapps. This puts CHRE in a normal state.
394      *
395      * This should only be used for a test environment, either through a
396      * @TestApi or development tools. This should not be used in a production
397      * environment.
398      *
399      * @param enable If true, put the context hub in test mode. If false, disable
400      *               test mode.
401      * @return       If true, the operation was successful; false otherwise.
402      */
setTestMode(boolean enable)403     public abstract boolean setTestMode(boolean enable);
404 
405     private static class ContextHubWrapperAidl extends IContextHubWrapper
406             implements IBinder.DeathRecipient {
407         private android.hardware.contexthub.IContextHub mHub;
408 
409         private final Map<Integer, ContextHubAidlCallback> mAidlCallbackMap =
410                     new HashMap<>();
411 
412         private Runnable mHandleServiceRestartCallback = null;
413 
414         // Use this thread in case where the execution requires to be on a service thread.
415         // For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission.
416         private HandlerThread mHandlerThread =
417                 new HandlerThread("Context Hub AIDL callback", Process.THREAD_PRIORITY_BACKGROUND);
418         private Handler mHandler;
419 
420         private class ContextHubAidlCallback extends
421                 android.hardware.contexthub.IContextHubCallback.Stub {
422             private final int mContextHubId;
423             private final ICallback mCallback;
424 
ContextHubAidlCallback(int contextHubId, ICallback callback)425             ContextHubAidlCallback(int contextHubId, ICallback callback) {
426                 mContextHubId = contextHubId;
427                 mCallback = callback;
428             }
429 
handleNanoappInfo(android.hardware.contexthub.NanoappInfo[] appInfo)430             public void handleNanoappInfo(android.hardware.contexthub.NanoappInfo[] appInfo) {
431                 List<NanoAppState> nanoAppStateList =
432                         ContextHubServiceUtil.createNanoAppStateList(appInfo);
433                 mHandler.post(() -> {
434                     mCallback.handleNanoappInfo(nanoAppStateList);
435                 });
436             }
437 
handleContextHubMessage(android.hardware.contexthub.ContextHubMessage msg, String[] msgContentPerms)438             public void handleContextHubMessage(android.hardware.contexthub.ContextHubMessage msg,
439                     String[] msgContentPerms) {
440                 mHandler.post(() -> {
441                     mCallback.handleNanoappMessage(
442                             (short) msg.hostEndPoint,
443                             ContextHubServiceUtil.createNanoAppMessage(msg),
444                             new ArrayList<>(Arrays.asList(msg.permissions)),
445                             new ArrayList<>(Arrays.asList(msgContentPerms)));
446                 });
447             }
448 
handleContextHubAsyncEvent(int evt)449             public void handleContextHubAsyncEvent(int evt) {
450                 mHandler.post(() -> {
451                     mCallback.handleContextHubEvent(
452                             ContextHubServiceUtil.toContextHubEventFromAidl(evt));
453                 });
454             }
455 
handleTransactionResult(int transactionId, boolean success)456             public void handleTransactionResult(int transactionId, boolean success) {
457                 mHandler.post(() -> {
458                     mCallback.handleTransactionResult(transactionId, success);
459                 });
460             }
461 
handleNanSessionRequest(NanSessionRequest request)462             public void handleNanSessionRequest(NanSessionRequest request) {
463                 // TODO(271471342): Implement
464             }
465 
466             @Override
getInterfaceHash()467             public String getInterfaceHash() {
468                 return android.hardware.contexthub.IContextHubCallback.HASH;
469             }
470 
471             @Override
getInterfaceVersion()472             public int getInterfaceVersion() {
473                 return android.hardware.contexthub.IContextHubCallback.VERSION;
474             }
475         }
476 
ContextHubWrapperAidl(android.hardware.contexthub.IContextHub hub)477         ContextHubWrapperAidl(android.hardware.contexthub.IContextHub hub) {
478             setHub(hub);
479             mHandlerThread.start();
480             mHandler = new Handler(mHandlerThread.getLooper());
481             linkWrapperToHubDeath();
482         }
483 
getHub()484         private synchronized android.hardware.contexthub.IContextHub getHub() {
485             return mHub;
486         }
487 
setHub(android.hardware.contexthub.IContextHub hub)488         private synchronized void setHub(android.hardware.contexthub.IContextHub hub) {
489             mHub = hub;
490         }
491 
492         @Override
binderDied()493         public void binderDied() {
494             Log.i(TAG, "Context Hub AIDL HAL died");
495 
496             setHub(maybeConnectToAidlGetProxy());
497             if (getHub() == null) {
498                 // TODO(b/256860015): Make this reconnection more robust
499                 Log.e(TAG, "Could not reconnect to Context Hub AIDL HAL");
500                 return;
501             }
502             linkWrapperToHubDeath();
503 
504             if (mHandleServiceRestartCallback != null) {
505                 mHandleServiceRestartCallback.run();
506             } else {
507                 Log.e(TAG, "mHandleServiceRestartCallback is not set");
508             }
509         }
510 
getHubs()511         public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
512             android.hardware.contexthub.IContextHub hub = getHub();
513             if (hub == null) {
514                 return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(),
515                         new ArrayList<String>());
516             }
517 
518             Set<String> supportedPermissions = new HashSet<>();
519             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
520             for (android.hardware.contexthub.ContextHubInfo hubInfo : hub.getContextHubs()) {
521                 hubInfoList.add(new ContextHubInfo(hubInfo));
522                 for (String permission : hubInfo.supportedPermissions) {
523                     supportedPermissions.add(permission);
524                 }
525             }
526             return new Pair(hubInfoList, new ArrayList<String>(supportedPermissions));
527         }
528 
supportsLocationSettingNotifications()529         public boolean supportsLocationSettingNotifications() {
530             return true;
531         }
532 
supportsWifiSettingNotifications()533         public boolean supportsWifiSettingNotifications() {
534             return true;
535         }
536 
supportsAirplaneModeSettingNotifications()537         public boolean supportsAirplaneModeSettingNotifications() {
538             return true;
539         }
540 
supportsMicrophoneSettingNotifications()541         public boolean supportsMicrophoneSettingNotifications() {
542             return true;
543         }
544 
supportsBtSettingNotifications()545         public boolean supportsBtSettingNotifications() {
546             return true;
547         }
548 
onLocationSettingChanged(boolean enabled)549         public void onLocationSettingChanged(boolean enabled) {
550             onSettingChanged(android.hardware.contexthub.Setting.LOCATION, enabled);
551         }
552 
onWifiSettingChanged(boolean enabled)553         public void onWifiSettingChanged(boolean enabled) {
554         }
555 
onAirplaneModeSettingChanged(boolean enabled)556         public void onAirplaneModeSettingChanged(boolean enabled) {
557             onSettingChanged(android.hardware.contexthub.Setting.AIRPLANE_MODE, enabled);
558         }
559 
onMicrophoneSettingChanged(boolean enabled)560         public void onMicrophoneSettingChanged(boolean enabled) {
561             onSettingChanged(android.hardware.contexthub.Setting.MICROPHONE, enabled);
562         }
563 
onWifiMainSettingChanged(boolean enabled)564         public void onWifiMainSettingChanged(boolean enabled) {
565             onSettingChanged(android.hardware.contexthub.Setting.WIFI_MAIN, enabled);
566         }
567 
onWifiScanningSettingChanged(boolean enabled)568         public void onWifiScanningSettingChanged(boolean enabled) {
569             onSettingChanged(android.hardware.contexthub.Setting.WIFI_SCANNING, enabled);
570         }
571 
onBtMainSettingChanged(boolean enabled)572         public void onBtMainSettingChanged(boolean enabled) {
573             onSettingChanged(android.hardware.contexthub.Setting.BT_MAIN, enabled);
574         }
575 
onBtScanningSettingChanged(boolean enabled)576         public void onBtScanningSettingChanged(boolean enabled) {
577             onSettingChanged(android.hardware.contexthub.Setting.BT_SCANNING, enabled);
578         }
579 
580         @Override
onHostEndpointConnected(HostEndpointInfo info)581         public void onHostEndpointConnected(HostEndpointInfo info) {
582             android.hardware.contexthub.IContextHub hub = getHub();
583             if (hub == null) {
584                 return;
585             }
586 
587             try {
588                 hub.onHostEndpointConnected(info);
589             } catch (RemoteException | ServiceSpecificException e) {
590                 Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage());
591             }
592         }
593 
594         @Override
onHostEndpointDisconnected(short hostEndpointId)595         public void onHostEndpointDisconnected(short hostEndpointId) {
596             android.hardware.contexthub.IContextHub hub = getHub();
597             if (hub == null) {
598                 return;
599             }
600 
601             try {
602                 hub.onHostEndpointDisconnected((char) hostEndpointId);
603             } catch (RemoteException | ServiceSpecificException e) {
604                 Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage());
605             }
606         }
607 
608         @ContextHubTransaction.Result
sendMessageToContextHub( short hostEndpointId, int contextHubId, NanoAppMessage message)609         public int sendMessageToContextHub(
610                 short hostEndpointId, int contextHubId, NanoAppMessage message)
611                 throws RemoteException {
612             android.hardware.contexthub.IContextHub hub = getHub();
613             if (hub == null) {
614                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
615             }
616 
617             try {
618                 hub.sendMessageToHub(contextHubId,
619                         ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message));
620                 return ContextHubTransaction.RESULT_SUCCESS;
621             } catch (RemoteException | ServiceSpecificException e) {
622                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
623             } catch (IllegalArgumentException e) {
624                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
625             }
626         }
627 
628         @ContextHubTransaction.Result
loadNanoapp(int contextHubId, NanoAppBinary binary, int transactionId)629         public int loadNanoapp(int contextHubId, NanoAppBinary binary,
630                 int transactionId) throws RemoteException {
631             android.hardware.contexthub.IContextHub hub = getHub();
632             if (hub == null) {
633                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
634             }
635 
636             android.hardware.contexthub.NanoappBinary aidlNanoAppBinary =
637                     ContextHubServiceUtil.createAidlNanoAppBinary(binary);
638             try {
639                 hub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
640                 return ContextHubTransaction.RESULT_SUCCESS;
641             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
642                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
643             } catch (IllegalArgumentException e) {
644                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
645             }
646         }
647 
648         @ContextHubTransaction.Result
unloadNanoapp(int contextHubId, long nanoappId, int transactionId)649         public int unloadNanoapp(int contextHubId, long nanoappId, int transactionId)
650                 throws RemoteException {
651             android.hardware.contexthub.IContextHub hub = getHub();
652             if (hub == null) {
653                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
654             }
655 
656             try {
657                 hub.unloadNanoapp(contextHubId, nanoappId, transactionId);
658                 return ContextHubTransaction.RESULT_SUCCESS;
659             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
660                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
661             } catch (IllegalArgumentException e) {
662                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
663             }
664         }
665 
666         @ContextHubTransaction.Result
enableNanoapp(int contextHubId, long nanoappId, int transactionId)667         public int enableNanoapp(int contextHubId, long nanoappId, int transactionId)
668                 throws RemoteException {
669             android.hardware.contexthub.IContextHub hub = getHub();
670             if (hub == null) {
671                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
672             }
673 
674             try {
675                 hub.enableNanoapp(contextHubId, nanoappId, transactionId);
676                 return ContextHubTransaction.RESULT_SUCCESS;
677             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
678                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
679             } catch (IllegalArgumentException e) {
680                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
681             }
682         }
683 
684         @ContextHubTransaction.Result
disableNanoapp(int contextHubId, long nanoappId, int transactionId)685         public int disableNanoapp(int contextHubId, long nanoappId, int transactionId)
686                 throws RemoteException {
687             android.hardware.contexthub.IContextHub hub = getHub();
688             if (hub == null) {
689                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
690             }
691 
692             try {
693                 hub.disableNanoapp(contextHubId, nanoappId, transactionId);
694                 return ContextHubTransaction.RESULT_SUCCESS;
695             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
696                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
697             } catch (IllegalArgumentException e) {
698                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
699             }
700         }
701 
702         @ContextHubTransaction.Result
queryNanoapps(int contextHubId)703         public int queryNanoapps(int contextHubId) throws RemoteException {
704             android.hardware.contexthub.IContextHub hub = getHub();
705             if (hub == null) {
706                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
707             }
708 
709             try {
710                 hub.queryNanoapps(contextHubId);
711                 return ContextHubTransaction.RESULT_SUCCESS;
712             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
713                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
714             } catch (IllegalArgumentException e) {
715                 return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
716             }
717         }
718 
getPreloadedNanoappIds(int contextHubId)719         public long[] getPreloadedNanoappIds(int contextHubId) {
720             android.hardware.contexthub.IContextHub hub = getHub();
721             if (hub == null) {
722                 return null;
723             }
724 
725             try {
726                 return hub.getPreloadedNanoappIds(contextHubId);
727             } catch (RemoteException e) {
728                 Log.e(TAG, "Exception while getting preloaded nanoapp IDs: " + e.getMessage());
729                 return null;
730             }
731         }
732 
registerExistingCallback(int contextHubId)733         public void registerExistingCallback(int contextHubId) {
734             android.hardware.contexthub.IContextHub hub = getHub();
735             if (hub == null) {
736                 return;
737             }
738 
739             ContextHubAidlCallback callback = mAidlCallbackMap.get(contextHubId);
740             if (callback == null) {
741                 Log.e(TAG, "Could not find existing callback to register for context hub ID = "
742                         + contextHubId);
743                 return;
744             }
745 
746             try {
747                 hub.registerCallback(contextHubId, callback);
748             } catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) {
749                 Log.e(TAG, "Exception while registering callback: " + e.getMessage());
750             }
751         }
752 
registerCallback(int contextHubId, ICallback callback)753         public void registerCallback(int contextHubId, ICallback callback) {
754             android.hardware.contexthub.IContextHub hub = getHub();
755             if (hub == null) {
756                 return;
757             }
758 
759             mHandleServiceRestartCallback = callback::handleServiceRestart;
760             mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
761             registerExistingCallback(contextHubId);
762         }
763 
setTestMode(boolean enable)764         public boolean setTestMode(boolean enable) {
765             android.hardware.contexthub.IContextHub hub = getHub();
766             if (hub == null) {
767                 return false;
768             }
769 
770             try {
771                 hub.setTestMode(enable);
772                 return true;
773             } catch (RemoteException | ServiceSpecificException e) {
774                 Log.e(TAG, "Exception while setting test mode (enable: "
775                         + (enable ? "true" : "false") + "): " + e.getMessage());
776                 return false;
777             }
778         }
779 
onSettingChanged(byte setting, boolean enabled)780         private void onSettingChanged(byte setting, boolean enabled) {
781             android.hardware.contexthub.IContextHub hub = getHub();
782             if (hub == null) {
783                 return;
784             }
785 
786             try {
787                 hub.onSettingChanged(setting, enabled);
788             } catch (RemoteException | ServiceSpecificException e) {
789                 Log.e(TAG, "Exception while sending setting update: " + e.getMessage());
790             }
791         }
792 
793         /**
794          * Links the mHub death handler to this
795          */
linkWrapperToHubDeath()796         private void linkWrapperToHubDeath() {
797             android.hardware.contexthub.IContextHub hub = getHub();
798             if (hub == null) {
799                 return;
800             }
801 
802             try {
803                 hub.asBinder().linkToDeath(this, 0);
804             } catch (RemoteException exception) {
805                 Log.e(TAG, "Context Hub AIDL service death receipt could not be linked");
806             }
807         }
808     }
809 
810     /**
811      * An abstract call that defines methods common to all HIDL IContextHubWrappers.
812      */
813     private abstract static class ContextHubWrapperHidl extends IContextHubWrapper {
814         private android.hardware.contexthub.V1_0.IContexthub mHub;
815 
816         protected ICallback mCallback = null;
817 
818         protected final Map<Integer, ContextHubWrapperHidlCallback> mHidlCallbackMap =
819                     new HashMap<>();
820 
821         protected class ContextHubWrapperHidlCallback extends IContexthubCallback.Stub {
822             private final int mContextHubId;
823             private final ICallback mCallback;
824 
ContextHubWrapperHidlCallback(int contextHubId, ICallback callback)825             ContextHubWrapperHidlCallback(int contextHubId, ICallback callback) {
826                 mContextHubId = contextHubId;
827                 mCallback = callback;
828             }
829 
830             @Override
handleClientMsg(ContextHubMsg message)831             public void handleClientMsg(ContextHubMsg message) {
832                 mCallback.handleNanoappMessage(
833                         message.hostEndPoint,
834                         ContextHubServiceUtil.createNanoAppMessage(message),
835                         Collections.emptyList() /* nanoappPermissions */,
836                         Collections.emptyList() /* messagePermissions */);
837             }
838 
839             @Override
handleTxnResult(int transactionId, int result)840             public void handleTxnResult(int transactionId, int result) {
841                 mCallback.handleTransactionResult(transactionId,
842                         result == TransactionResult.SUCCESS);
843             }
844 
845             @Override
handleHubEvent(int eventType)846             public void handleHubEvent(int eventType) {
847                 mCallback.handleContextHubEvent(
848                         ContextHubServiceUtil.toContextHubEvent(eventType));
849             }
850 
851             @Override
handleAppAbort(long nanoAppId, int abortCode)852             public void handleAppAbort(long nanoAppId, int abortCode) {
853                 mCallback.handleNanoappAbort(nanoAppId, abortCode);
854             }
855 
856             @Override
handleAppsInfo( ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> nanoAppInfoList)857             public void handleAppsInfo(
858                     ArrayList<android.hardware.contexthub.V1_0.HubAppInfo> nanoAppInfoList) {
859                 handleAppsInfo_1_2(ContextHubServiceUtil.toHubAppInfo_1_2(nanoAppInfoList));
860             }
861 
862             @Override
handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message, ArrayList<String> messagePermissions)863             public void handleClientMsg_1_2(android.hardware.contexthub.V1_2.ContextHubMsg message,
864                     ArrayList<String> messagePermissions) {
865                 mCallback.handleNanoappMessage(
866                         message.msg_1_0.hostEndPoint,
867                         ContextHubServiceUtil.createNanoAppMessage(message.msg_1_0),
868                         message.permissions, messagePermissions);
869             }
870 
871             @Override
handleAppsInfo_1_2(ArrayList<HubAppInfo> nanoAppInfoList)872             public void handleAppsInfo_1_2(ArrayList<HubAppInfo> nanoAppInfoList) {
873                 List<NanoAppState> nanoAppStateList =
874                         ContextHubServiceUtil.createNanoAppStateList(nanoAppInfoList);
875                 mCallback.handleNanoappInfo(nanoAppStateList);
876             }
877         }
878 
ContextHubWrapperHidl(android.hardware.contexthub.V1_0.IContexthub hub)879         ContextHubWrapperHidl(android.hardware.contexthub.V1_0.IContexthub hub) {
880             mHub = hub;
881         }
882 
883         @ContextHubTransaction.Result
sendMessageToContextHub( short hostEndpointId, int contextHubId, NanoAppMessage message)884         public int sendMessageToContextHub(
885                 short hostEndpointId, int contextHubId, NanoAppMessage message)
886                 throws RemoteException {
887             ContextHubMsg messageToNanoApp =
888                     ContextHubServiceUtil.createHidlContextHubMessage(hostEndpointId, message);
889             return ContextHubServiceUtil.toTransactionResult(
890                     mHub.sendMessageToHub(contextHubId, messageToNanoApp));
891         }
892 
893         @ContextHubTransaction.Result
loadNanoapp(int contextHubId, NanoAppBinary binary, int transactionId)894         public int loadNanoapp(int contextHubId, NanoAppBinary binary,
895                 int transactionId) throws RemoteException {
896             android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
897                     ContextHubServiceUtil.createHidlNanoAppBinary(binary);
898             return ContextHubServiceUtil.toTransactionResult(mHub.loadNanoApp(
899                     contextHubId, hidlNanoAppBinary, transactionId));
900         }
901 
902         @ContextHubTransaction.Result
unloadNanoapp(int contextHubId, long nanoappId, int transactionId)903         public int unloadNanoapp(int contextHubId, long nanoappId, int transactionId)
904                 throws RemoteException {
905             return ContextHubServiceUtil.toTransactionResult(mHub.unloadNanoApp(
906                     contextHubId, nanoappId, transactionId));
907         }
908 
909         @ContextHubTransaction.Result
enableNanoapp(int contextHubId, long nanoappId, int transactionId)910         public int enableNanoapp(int contextHubId, long nanoappId, int transactionId)
911                 throws RemoteException {
912             return ContextHubServiceUtil.toTransactionResult(mHub.enableNanoApp(
913                     contextHubId, nanoappId, transactionId));
914         }
915 
916         @ContextHubTransaction.Result
disableNanoapp(int contextHubId, long nanoappId, int transactionId)917         public int disableNanoapp(int contextHubId, long nanoappId, int transactionId)
918                 throws RemoteException {
919             return ContextHubServiceUtil.toTransactionResult(mHub.disableNanoApp(
920                     contextHubId, nanoappId, transactionId));
921         }
922 
923         @ContextHubTransaction.Result
queryNanoapps(int contextHubId)924         public int queryNanoapps(int contextHubId) throws RemoteException {
925             return ContextHubServiceUtil.toTransactionResult(
926                     mHub.queryApps(contextHubId));
927         }
928 
getPreloadedNanoappIds(int contextHubId)929         public long[] getPreloadedNanoappIds(int contextHubId) {
930             return new long[0];
931         }
932 
registerCallback(int contextHubId, ICallback callback)933         public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
934             mHidlCallbackMap.put(contextHubId,
935                         new ContextHubWrapperHidlCallback(contextHubId, callback));
936             mHub.registerCallback(contextHubId, mHidlCallbackMap.get(contextHubId));
937         }
938 
registerExistingCallback(int contextHubId)939         public void registerExistingCallback(int contextHubId) throws RemoteException {
940             ContextHubWrapperHidlCallback callback = mHidlCallbackMap.get(contextHubId);
941             if (callback == null) {
942                 Log.e(TAG, "Could not find existing callback for context hub with ID = "
943                         + contextHubId);
944                 return;
945             }
946 
947             mHub.registerCallback(contextHubId, callback);
948         }
949 
setTestMode(boolean enable)950         public boolean setTestMode(boolean enable) {
951             return false;
952         }
953 
supportsBtSettingNotifications()954         public boolean supportsBtSettingNotifications() {
955             return false;
956         }
957 
onWifiMainSettingChanged(boolean enabled)958         public void onWifiMainSettingChanged(boolean enabled) {}
onWifiScanningSettingChanged(boolean enabled)959         public void onWifiScanningSettingChanged(boolean enabled) {}
onBtMainSettingChanged(boolean enabled)960         public void onBtMainSettingChanged(boolean enabled) {}
onBtScanningSettingChanged(boolean enabled)961         public void onBtScanningSettingChanged(boolean enabled) {}
962     }
963 
964     private static class ContextHubWrapperV1_0 extends ContextHubWrapperHidl {
965         private android.hardware.contexthub.V1_0.IContexthub mHub;
966 
ContextHubWrapperV1_0(android.hardware.contexthub.V1_0.IContexthub hub)967         ContextHubWrapperV1_0(android.hardware.contexthub.V1_0.IContexthub hub) {
968             super(hub);
969             mHub = hub;
970         }
971 
getHubs()972         public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
973             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
974             for (ContextHub hub : mHub.getHubs()) {
975                 hubInfoList.add(new ContextHubInfo(hub));
976             }
977             return new Pair(hubInfoList, new ArrayList<String>());
978         }
979 
supportsLocationSettingNotifications()980         public boolean supportsLocationSettingNotifications() {
981             return false;
982         }
983 
supportsWifiSettingNotifications()984         public boolean supportsWifiSettingNotifications() {
985             return false;
986         }
987 
supportsAirplaneModeSettingNotifications()988         public boolean supportsAirplaneModeSettingNotifications() {
989             return false;
990         }
991 
supportsMicrophoneSettingNotifications()992         public boolean supportsMicrophoneSettingNotifications() {
993             return false;
994         }
995 
onLocationSettingChanged(boolean enabled)996         public void onLocationSettingChanged(boolean enabled) {
997         }
998 
onWifiSettingChanged(boolean enabled)999         public void onWifiSettingChanged(boolean enabled) {
1000         }
1001 
onAirplaneModeSettingChanged(boolean enabled)1002         public void onAirplaneModeSettingChanged(boolean enabled) {
1003         }
1004 
onMicrophoneSettingChanged(boolean enabled)1005         public void onMicrophoneSettingChanged(boolean enabled) {
1006         }
1007     }
1008 
1009     private static class ContextHubWrapperV1_1 extends ContextHubWrapperHidl {
1010         private android.hardware.contexthub.V1_1.IContexthub mHub;
1011 
ContextHubWrapperV1_1(android.hardware.contexthub.V1_1.IContexthub hub)1012         ContextHubWrapperV1_1(android.hardware.contexthub.V1_1.IContexthub hub) {
1013             super(hub);
1014             mHub = hub;
1015         }
1016 
getHubs()1017         public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
1018             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
1019             for (ContextHub hub : mHub.getHubs()) {
1020                 hubInfoList.add(new ContextHubInfo(hub));
1021             }
1022             return new Pair(hubInfoList, new ArrayList<String>());
1023         }
1024 
supportsLocationSettingNotifications()1025         public boolean supportsLocationSettingNotifications() {
1026             return true;
1027         }
1028 
supportsWifiSettingNotifications()1029         public boolean supportsWifiSettingNotifications() {
1030             return false;
1031         }
1032 
supportsAirplaneModeSettingNotifications()1033         public boolean supportsAirplaneModeSettingNotifications() {
1034             return false;
1035         }
1036 
supportsMicrophoneSettingNotifications()1037         public boolean supportsMicrophoneSettingNotifications() {
1038             return false;
1039         }
1040 
onLocationSettingChanged(boolean enabled)1041         public void onLocationSettingChanged(boolean enabled) {
1042             try {
1043                 mHub.onSettingChanged(Setting.LOCATION,
1044                         enabled ? SettingValue.ENABLED : SettingValue.DISABLED);
1045             } catch (RemoteException e) {
1046                 Log.e(TAG, "Failed to send setting change to Contexthub", e);
1047             }
1048         }
1049 
onWifiSettingChanged(boolean enabled)1050         public void onWifiSettingChanged(boolean enabled) {
1051         }
1052 
onAirplaneModeSettingChanged(boolean enabled)1053         public void onAirplaneModeSettingChanged(boolean enabled) {
1054         }
1055 
onMicrophoneSettingChanged(boolean enabled)1056         public void onMicrophoneSettingChanged(boolean enabled) {
1057         }
1058     }
1059 
1060     private static class ContextHubWrapperV1_2 extends ContextHubWrapperHidl
1061             implements android.hardware.contexthub.V1_2.IContexthub.getHubs_1_2Callback {
1062         private final android.hardware.contexthub.V1_2.IContexthub mHub;
1063 
1064         private Pair<List<ContextHubInfo>, List<String>> mHubInfo =
1065                 new Pair<>(Collections.emptyList(), Collections.emptyList());
1066 
ContextHubWrapperV1_2(android.hardware.contexthub.V1_2.IContexthub hub)1067         ContextHubWrapperV1_2(android.hardware.contexthub.V1_2.IContexthub hub) {
1068             super(hub);
1069             mHub = hub;
1070         }
1071 
1072         @Override
onValues(ArrayList<ContextHub> hubs, ArrayList<String> supportedPermissions)1073         public void onValues(ArrayList<ContextHub> hubs, ArrayList<String> supportedPermissions) {
1074             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
1075             for (ContextHub hub : hubs) {
1076                 hubInfoList.add(new ContextHubInfo(hub));
1077             }
1078             mHubInfo = new Pair(hubInfoList, supportedPermissions);
1079         }
1080 
getHubs()1081         public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
1082             mHub.getHubs_1_2(this);
1083             return mHubInfo;
1084         }
1085 
supportsLocationSettingNotifications()1086         public boolean supportsLocationSettingNotifications() {
1087             return true;
1088         }
1089 
supportsWifiSettingNotifications()1090         public boolean supportsWifiSettingNotifications() {
1091             return true;
1092         }
1093 
supportsAirplaneModeSettingNotifications()1094         public boolean supportsAirplaneModeSettingNotifications() {
1095             return true;
1096         }
1097 
supportsMicrophoneSettingNotifications()1098         public boolean supportsMicrophoneSettingNotifications() {
1099             return true;
1100         }
1101 
onLocationSettingChanged(boolean enabled)1102         public void onLocationSettingChanged(boolean enabled) {
1103             sendSettingChanged(Setting.LOCATION,
1104                     enabled ? SettingValue.ENABLED : SettingValue.DISABLED);
1105         }
1106 
onWifiSettingChanged(boolean enabled)1107         public void onWifiSettingChanged(boolean enabled) {
1108             sendSettingChanged(android.hardware.contexthub.V1_2.Setting.WIFI_AVAILABLE,
1109                     enabled ? SettingValue.ENABLED : SettingValue.DISABLED);
1110         }
1111 
onAirplaneModeSettingChanged(boolean enabled)1112         public void onAirplaneModeSettingChanged(boolean enabled) {
1113             sendSettingChanged(android.hardware.contexthub.V1_2.Setting.AIRPLANE_MODE,
1114                     enabled ? SettingValue.ENABLED : SettingValue.DISABLED);
1115         }
1116 
onMicrophoneSettingChanged(boolean enabled)1117         public void onMicrophoneSettingChanged(boolean enabled) {
1118             sendSettingChanged(android.hardware.contexthub.V1_2.Setting.MICROPHONE,
1119                     enabled ? SettingValue.ENABLED : SettingValue.DISABLED);
1120         }
1121 
registerCallback(int contextHubId, ICallback callback)1122         public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
1123             mHidlCallbackMap.put(contextHubId,
1124                         new ContextHubWrapperHidlCallback(contextHubId, callback));
1125             mHub.registerCallback_1_2(contextHubId, mHidlCallbackMap.get(contextHubId));
1126         }
1127 
sendSettingChanged(byte setting, byte newValue)1128         private void sendSettingChanged(byte setting, byte newValue) {
1129             try {
1130                 mHub.onSettingChanged_1_2(setting, newValue);
1131             } catch (RemoteException e) {
1132                 Log.e(TAG, "Failed to send setting change to Contexthub", e);
1133             }
1134         }
1135     }
1136 }
1137