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
17package android.hardware.location;
18
19import android.Manifest;
20import android.content.Context;
21import android.os.RemoteCallbackList;
22import android.os.RemoteException;
23import android.text.TextUtils;
24import 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 */
32public 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
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
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
87    public static boolean isSupported() {
88        return nativeIsSupported();
89    }
90
91    @Override
92    public String[] getSupportedActivities() {
93        checkPermissions();
94        return mSupportedActivities;
95    }
96
97    @Override
98    public boolean isActivitySupported(String activity) {
99        checkPermissions();
100        int activityType = getActivityType(activity);
101        return activityType != INVALID_ACTIVITY_TYPE;
102    }
103
104    @Override
105    public boolean registerSink(IActivityRecognitionHardwareSink sink) {
106        checkPermissions();
107        return mSinks.register(sink);
108    }
109
110    @Override
111    public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
112        checkPermissions();
113        return mSinks.unregister(sink);
114    }
115
116    @Override
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
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
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     */
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
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
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
218    private void checkPermissions() {
219        mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
220    }
221
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
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
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
265    static { nativeClassInit(); }
266
267    private static native void nativeClassInit();
268    private static native boolean nativeIsSupported();
269
270    private native void nativeInitialize();
271    private native void nativeRelease();
272    private native String[] nativeGetSupportedActivities();
273    private native int nativeEnableActivityEvent(
274            int activityType,
275            int eventType,
276            long reportLatenceNs);
277    private native int nativeDisableActivityEvent(int activityType, int eventType);
278    private native int nativeFlush();
279}
280