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 = "ActivityRecognitionHardware";
34
35    private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
36    private static final int INVALID_ACTIVITY_TYPE = -1;
37    private static final int NATIVE_SUCCESS_RESULT = 0;
38
39    private static ActivityRecognitionHardware sSingletonInstance = null;
40    private static final Object sSingletonInstanceLock = new Object();
41
42    private final Context mContext;
43    private final String[] mSupportedActivities;
44
45    private final RemoteCallbackList<IActivityRecognitionHardwareSink> mSinks =
46            new RemoteCallbackList<IActivityRecognitionHardwareSink>();
47
48    private static class Event {
49        public int activity;
50        public int type;
51        public long timestamp;
52    }
53
54    private ActivityRecognitionHardware(Context context) {
55        nativeInitialize();
56
57        mContext = context;
58        mSupportedActivities = fetchSupportedActivities();
59    }
60
61    public static ActivityRecognitionHardware getInstance(Context context) {
62        synchronized (sSingletonInstanceLock) {
63            if (sSingletonInstance == null) {
64                sSingletonInstance = new ActivityRecognitionHardware(context);
65            }
66
67            return sSingletonInstance;
68        }
69    }
70
71    public static boolean isSupported() {
72        return nativeIsSupported();
73    }
74
75    @Override
76    public String[] getSupportedActivities() {
77        checkPermissions();
78        return mSupportedActivities;
79    }
80
81    @Override
82    public boolean isActivitySupported(String activity) {
83        checkPermissions();
84        int activityType = getActivityType(activity);
85        return activityType != INVALID_ACTIVITY_TYPE;
86    }
87
88    @Override
89    public boolean registerSink(IActivityRecognitionHardwareSink sink) {
90        checkPermissions();
91        return mSinks.register(sink);
92    }
93
94    @Override
95    public boolean unregisterSink(IActivityRecognitionHardwareSink sink) {
96        checkPermissions();
97        return mSinks.unregister(sink);
98    }
99
100    @Override
101    public boolean enableActivityEvent(String activity, int eventType, long reportLatencyNs) {
102        checkPermissions();
103
104        int activityType = getActivityType(activity);
105        if (activityType == INVALID_ACTIVITY_TYPE) {
106            return false;
107        }
108
109        int result = nativeEnableActivityEvent(activityType, eventType, reportLatencyNs);
110        return result == NATIVE_SUCCESS_RESULT;
111    }
112
113    @Override
114    public boolean disableActivityEvent(String activity, int eventType) {
115        checkPermissions();
116
117        int activityType = getActivityType(activity);
118        if (activityType == INVALID_ACTIVITY_TYPE) {
119            return false;
120        }
121
122        int result = nativeDisableActivityEvent(activityType, eventType);
123        return result == NATIVE_SUCCESS_RESULT;
124    }
125
126    @Override
127    public boolean flush() {
128        checkPermissions();
129        int result = nativeFlush();
130        return result == NATIVE_SUCCESS_RESULT;
131    }
132
133    /**
134     * Called by the Activity-Recognition HAL.
135     */
136    private void onActivityChanged(Event[] events) {
137        if (events == null || events.length == 0) {
138            Log.d(TAG, "No events to broadcast for onActivityChanged.");
139            return;
140        }
141
142        int eventsLength = events.length;
143        ActivityRecognitionEvent activityRecognitionEventArray[] =
144                new ActivityRecognitionEvent[eventsLength];
145        for (int i = 0; i < eventsLength; ++i) {
146            Event event = events[i];
147            String activityName = getActivityName(event.activity);
148            activityRecognitionEventArray[i] =
149                    new ActivityRecognitionEvent(activityName, event.type, event.timestamp);
150        }
151        ActivityChangedEvent activityChangedEvent =
152                new ActivityChangedEvent(activityRecognitionEventArray);
153
154        int size = mSinks.beginBroadcast();
155        for (int i = 0; i < size; ++i) {
156            IActivityRecognitionHardwareSink sink = mSinks.getBroadcastItem(i);
157            try {
158                sink.onActivityChanged(activityChangedEvent);
159            } catch (RemoteException e) {
160                Log.e(TAG, "Error delivering activity changed event.", e);
161            }
162        }
163        mSinks.finishBroadcast();
164
165    }
166
167    private String getActivityName(int activityType) {
168        if (activityType < 0 || activityType >= mSupportedActivities.length) {
169            String message = String.format(
170                    "Invalid ActivityType: %d, SupportedActivities: %d",
171                    activityType,
172                    mSupportedActivities.length);
173            Log.e(TAG, message);
174            return null;
175        }
176
177        return mSupportedActivities[activityType];
178    }
179
180    private int getActivityType(String activity) {
181        if (TextUtils.isEmpty(activity)) {
182            return INVALID_ACTIVITY_TYPE;
183        }
184
185        int supportedActivitiesLength = mSupportedActivities.length;
186        for (int i = 0; i < supportedActivitiesLength; ++i) {
187            if (activity.equals(mSupportedActivities[i])) {
188                return i;
189            }
190        }
191
192        return INVALID_ACTIVITY_TYPE;
193    }
194
195    private void checkPermissions() {
196        String message = String.format(
197                "Permission '%s' not granted to access ActivityRecognitionHardware",
198                HARDWARE_PERMISSION);
199        mContext.enforceCallingPermission(HARDWARE_PERMISSION, message);
200    }
201
202    private String[] fetchSupportedActivities() {
203        String[] supportedActivities = nativeGetSupportedActivities();
204        if (supportedActivities != null) {
205            return supportedActivities;
206        }
207
208        return new String[0];
209    }
210
211    // native bindings
212    static { nativeClassInit(); }
213
214    private static native void nativeClassInit();
215    private static native boolean nativeIsSupported();
216
217    private native void nativeInitialize();
218    private native void nativeRelease();
219    private native String[] nativeGetSupportedActivities();
220    private native int nativeEnableActivityEvent(
221            int activityType,
222            int eventType,
223            long reportLatenceNs);
224    private native int nativeDisableActivityEvent(int activityType, int eventType);
225    private native int nativeFlush();
226}
227