1 /*
2  * Copyright (C) 2014 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 
17 package android.hardware.location;
18 
19 import android.Manifest;
20 import android.content.Context;
21 import android.os.RemoteCallbackList;
22 import android.os.RemoteException;
23 import android.text.TextUtils;
24 import android.util.Log;
25 
26 /**
27  * A class that implements an {@link IActivityRecognitionHardware} backed up by the Activity
28  * Recognition HAL.
29  *
30  * @hide
31  */
32 public class ActivityRecognitionHardware extends IActivityRecognitionHardware.Stub {
33     private static final String TAG = "ActivityRecognitionHW";
34     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
35 
36     private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
37     private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
38             + HARDWARE_PERMISSION + "' not granted to access ActivityRecognitionHardware";
39 
40     private static final int INVALID_ACTIVITY_TYPE = -1;
41     private static final int NATIVE_SUCCESS_RESULT = 0;
42     private static final int EVENT_TYPE_DISABLED = 0;
43     private static final int EVENT_TYPE_ENABLED = 1;
44 
45     /**
46      * Contains the number of supported Event Types.
47      *
48      * NOTE: increment this counter every time a new EVENT_TYPE_ is added to
49      *       com.android.location.provider.ActivityRecognitionProvider
50      */
51     private static final int EVENT_TYPE_COUNT = 3;
52 
53     private static ActivityRecognitionHardware sSingletonInstance;
54     private static final Object sSingletonInstanceLock = new Object();
55 
56     private final Context mContext;
57     private final int mSupportedActivitiesCount;
58     private final String[] mSupportedActivities;
59     private final int[][] mSupportedActivitiesEnabledEvents;
60     private final SinkList mSinks = new SinkList();
61 
62     private static class Event {
63         public int activity;
64         public int type;
65         public long timestamp;
66     }
67 
ActivityRecognitionHardware(Context context)68     private ActivityRecognitionHardware(Context context) {
69         nativeInitialize();
70 
71         mContext = context;
72         mSupportedActivities = fetchSupportedActivities();
73         mSupportedActivitiesCount = mSupportedActivities.length;
74         mSupportedActivitiesEnabledEvents = new int[mSupportedActivitiesCount][EVENT_TYPE_COUNT];
75     }
76 
getInstance(Context context)77     public static ActivityRecognitionHardware getInstance(Context context) {
78         synchronized (sSingletonInstanceLock) {
79             if (sSingletonInstance == null) {
80                 sSingletonInstance = new ActivityRecognitionHardware(context);
81             }
82 
83             return sSingletonInstance;
84         }
85     }
86 
isSupported()87     public static boolean isSupported() {
88         return nativeIsSupported();
89     }
90 
91     @Override
getSupportedActivities()92     public String[] getSupportedActivities() {
93         checkPermissions();
94         return mSupportedActivities;
95     }
96 
97     @Override
isActivitySupported(String activity)98     public boolean isActivitySupported(String activity) {
99         checkPermissions();
100         int activityType = getActivityType(activity);
101         return activityType != INVALID_ACTIVITY_TYPE;
102     }
103 
104     @Override
registerSink(IActivityRecognitionHardwareSink sink)105     public boolean registerSink(IActivityRecognitionHardwareSink sink) {
106         checkPermissions();
107         return mSinks.register(sink);
108     }
109 
110     @Override
unregisterSink(IActivityRecognitionHardwareSink sink)111     public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
112         checkPermissions();
113         return mSinks.unregister(sink);
114     }
115 
116     @Override
enableActivityEvent(String activity, int eventType, long reportLatencyNs)117     public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
118         checkPermissions();
119 
120         int activityType = getActivityType(activity);
121         if (activityType == INVALID_ACTIVITY_TYPE) {
122             return false;
123         }
124 
125         int result = nativeEnableActivityEvent(activityType, eventType, reportLatencyNs);
126         if (result == NATIVE_SUCCESS_RESULT) {
127             mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_ENABLED;
128             return true;
129         }
130         return false;
131     }
132 
133     @Override
disableActivityEvent(String activity, int eventType)134     public boolean disableActivityEvent(String activity, int eventType) {
135         checkPermissions();
136 
137         int activityType = getActivityType(activity);
138         if (activityType == INVALID_ACTIVITY_TYPE) {
139             return false;
140         }
141 
142         int result = nativeDisableActivityEvent(activityType, eventType);
143         if (result == NATIVE_SUCCESS_RESULT) {
144             mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
145             return true;
146         }
147         return false;
148     }
149 
150     @Override
flush()151     public boolean flush() {
152         checkPermissions();
153         int result = nativeFlush();
154         return result == NATIVE_SUCCESS_RESULT;
155     }
156 
157     /**
158      * Called by the Activity-Recognition HAL.
159      */
onActivityChanged(Event[] events)160     private void onActivityChanged(Event[] events) {
161         if (events == null || events.length == 0) {
162             if (DEBUG) Log.d(TAG, "No events to broadcast for onActivityChanged.");
163             return;
164         }
165 
166         int eventsLength = events.length;
167         ActivityRecognitionEvent activityRecognitionEventArray[] =
168                 new ActivityRecognitionEvent[eventsLength];
169         for (int i = 0; i < eventsLength; ++i) {
170             Event event = events[i];
171             String activityName = getActivityName(event.activity);
172             activityRecognitionEventArray[i] =
173                     new ActivityRecognitionEvent(activityName, event.type, event.timestamp);
174         }
175         ActivityChangedEvent activityChangedEvent =
176                 new ActivityChangedEvent(activityRecognitionEventArray);
177 
178         int size = mSinks.beginBroadcast();
179         for (int i = 0; i < size; ++i) {
180             IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i);
181             try {
182                 sink.onActivityChanged(activityChangedEvent);
183             } catch (RemoteException e) {
184                 Log.e(TAG, "Error delivering activity changed event.", e);
185             }
186         }
187         mSinks.finishBroadcast();
188     }
189 
getActivityName(int activityType)190     private String getActivityName(int activityType) {
191         if (activityType < 0 || activityType >= mSupportedActivities.length) {
192             String message = String.format(
193                     "Invalid ActivityType: %d, SupportedActivities: %d",
194                     activityType,
195                     mSupportedActivities.length);
196             Log.e(TAG, message);
197             return null;
198         }
199 
200         return mSupportedActivities[activityType];
201     }
202 
getActivityType(String activity)203     private int getActivityType(String activity) {
204         if (TextUtils.isEmpty(activity)) {
205             return INVALID_ACTIVITY_TYPE;
206         }
207 
208         int supportedActivitiesLength = mSupportedActivities.length;
209         for (int i = 0; i < supportedActivitiesLength; ++i) {
210             if (activity.equals(mSupportedActivities[i])) {
211                 return i;
212             }
213         }
214 
215         return INVALID_ACTIVITY_TYPE;
216     }
217 
checkPermissions()218     private void checkPermissions() {
219         mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
220     }
221 
fetchSupportedActivities()222     private String[] fetchSupportedActivities() {
223         String[] supportedActivities = nativeGetSupportedActivities();
224         if (supportedActivities != null) {
225             return supportedActivities;
226         }
227 
228         return new String[0];
229     }
230 
231     private class SinkList extends RemoteCallbackList<IActivityRecognitionHardwareSink> {
232         @Override
onCallbackDied(IActivityRecognitionHardwareSink callback)233         public void onCallbackDied(IActivityRecognitionHardwareSink callback) {
234             int callbackCount = mSinks.getRegisteredCallbackCount();
235             if (DEBUG) Log.d(TAG, "RegisteredCallbackCount: " + callbackCount);
236             if (callbackCount != 0) {
237                 return;
238             }
239             // currently there is only one client for this, so if all its sinks have died, we clean
240             // up after them, this ensures that the AR HAL is not out of sink
241             for (int activity = 0; activity < mSupportedActivitiesCount; ++activity) {
242                 for (int event = 0; event < EVENT_TYPE_COUNT; ++event) {
243                     disableActivityEventIfEnabled(activity, event);
244                 }
245             }
246         }
247 
disableActivityEventIfEnabled(int activityType, int eventType)248         private void disableActivityEventIfEnabled(int activityType, int eventType) {
249             if (mSupportedActivitiesEnabledEvents[activityType][eventType] != EVENT_TYPE_ENABLED) {
250                 return;
251             }
252 
253             int result = nativeDisableActivityEvent(activityType, eventType);
254             mSupportedActivitiesEnabledEvents[activityType][eventType] = EVENT_TYPE_DISABLED;
255             String message = String.format(
256                     "DisableActivityEvent: activityType=%d, eventType=%d, result=%d",
257                     activityType,
258                     eventType,
259                     result);
260             Log.e(TAG, message);
261         }
262     }
263 
264     // native bindings
nativeClassInit()265     static { nativeClassInit(); }
266 
nativeClassInit()267     private static native void nativeClassInit();
nativeIsSupported()268     private static native boolean nativeIsSupported();
269 
nativeInitialize()270     private native void nativeInitialize();
nativeRelease()271     private native void nativeRelease();
nativeGetSupportedActivities()272     private native String[] nativeGetSupportedActivities();
nativeEnableActivityEvent( int activityType, int eventType, long reportLatenceNs)273     private native int nativeEnableActivityEvent(
274             int activityType,
275             int eventType,
276             long reportLatenceNs);
nativeDisableActivityEvent(int activityType, int eventType)277     private native int nativeDisableActivityEvent(int activityType, int eventType);
nativeFlush()278     private native int nativeFlush();
279 }
280