blob: 91f652056326ece1d28b44d8bb049c18fb1ad41c [file] [log] [blame]
Blazej Magnowski72323322015-07-24 11:49:40 -07001/*
2 * Copyright (C) 2015 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 com.android.systemui.analytics;
18
19import android.content.Context;
20import android.database.ContentObserver;
21import android.hardware.Sensor;
22import android.hardware.SensorEvent;
23import android.hardware.SensorEventListener;
Blazej Magnowski72323322015-07-24 11:49:40 -070024import android.os.AsyncTask;
25import android.os.Build;
26import android.os.Handler;
27import android.os.UserHandle;
28import android.provider.Settings;
29import android.util.Log;
30import android.view.MotionEvent;
31
Blazej Magnowski72323322015-07-24 11:49:40 -070032import java.io.File;
33import java.io.FileOutputStream;
34import java.io.IOException;
35
36import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session;
37import static com.android.systemui.statusbar.phone.TouchAnalyticsProto.Session.PhoneEvent;
38
39/**
40 * Tracks touch, sensor and phone events when the lockscreen is on. If the phone is unlocked
41 * the data containing these events is saved to a file. This data is collected
42 * to analyze how a human interaction looks like.
43 *
44 * A session starts when the screen is turned on.
45 * A session ends when the screen is turned off or user unlocks the phone.
46 */
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070047public class DataCollector implements SensorEventListener {
48 private static final String TAG = "DataCollector";
49 private static final String COLLECTOR_ENABLE = "data_collector_enable";
50 private static final String COLLECT_BAD_TOUCHES = "data_collector_collect_bad_touches";
Blazej Magnowski72323322015-07-24 11:49:40 -070051
52 private static final long TIMEOUT_MILLIS = 11000; // 11 seconds.
53 public static final boolean DEBUG = false;
54
Blazej Magnowski72323322015-07-24 11:49:40 -070055 private final Handler mHandler = new Handler();
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070056 private final Context mContext;
57
58 // Err on the side of caution, so logging is not started after a crash even tough the screen
59 // is off.
60 private SensorLoggerSession mCurrentSession = null;
61
62 private boolean mEnableCollector = false;
63 private boolean mTimeoutActive = false;
64 private boolean mCollectBadTouches = false;
65 private boolean mCornerSwiping = false;
66 private boolean mTrackingStarted = false;
67
68 private static DataCollector sInstance = null;
69
Blazej Magnowski72323322015-07-24 11:49:40 -070070 protected final ContentObserver mSettingsObserver = new ContentObserver(mHandler) {
71 @Override
72 public void onChange(boolean selfChange) {
73 updateConfiguration();
74 }
75 };
76
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070077 private DataCollector(Context context) {
Blazej Magnowski72323322015-07-24 11:49:40 -070078 mContext = context;
79
80 mContext.getContentResolver().registerContentObserver(
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070081 Settings.Secure.getUriFor(COLLECTOR_ENABLE), false,
Blazej Magnowski72323322015-07-24 11:49:40 -070082 mSettingsObserver,
83 UserHandle.USER_ALL);
84
85 mContext.getContentResolver().registerContentObserver(
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070086 Settings.Secure.getUriFor(COLLECT_BAD_TOUCHES), false,
Blazej Magnowski72323322015-07-24 11:49:40 -070087 mSettingsObserver,
88 UserHandle.USER_ALL);
89
90 updateConfiguration();
91 }
92
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070093 public static DataCollector getInstance(Context context) {
Blazej Magnowski72323322015-07-24 11:49:40 -070094 if (sInstance == null) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -070095 sInstance = new DataCollector(context);
Blazej Magnowski72323322015-07-24 11:49:40 -070096 }
97 return sInstance;
98 }
99
100 private void updateConfiguration() {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700101 mEnableCollector = Build.IS_DEBUGGABLE && 0 != Settings.Secure.getInt(
Blazej Magnowski72323322015-07-24 11:49:40 -0700102 mContext.getContentResolver(),
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700103 COLLECTOR_ENABLE, 0);
104 mCollectBadTouches = mEnableCollector && 0 != Settings.Secure.getInt(
Blazej Magnowski72323322015-07-24 11:49:40 -0700105 mContext.getContentResolver(),
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700106 COLLECT_BAD_TOUCHES, 0);
Blazej Magnowski72323322015-07-24 11:49:40 -0700107 }
108
109 private boolean sessionEntrypoint() {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700110 if (mEnableCollector && mCurrentSession == null) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700111 onSessionStart();
112 return true;
113 }
114 return false;
115 }
116
117 private void sessionExitpoint(int result) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700118 if (mEnableCollector && mCurrentSession != null) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700119 onSessionEnd(result);
120 }
121 }
122
123 private void onSessionStart() {
Blazej Magnowski72323322015-07-24 11:49:40 -0700124 mCornerSwiping = false;
125 mTrackingStarted = false;
126 mCurrentSession = new SensorLoggerSession(System.currentTimeMillis(), System.nanoTime());
Blazej Magnowski72323322015-07-24 11:49:40 -0700127 }
128
129 private void onSessionEnd(int result) {
130 SensorLoggerSession session = mCurrentSession;
131 mCurrentSession = null;
132
133 session.end(System.currentTimeMillis(), result);
134 queueSession(session);
135 }
136
137 private void queueSession(final SensorLoggerSession currentSession) {
138 AsyncTask.execute(new Runnable() {
139 @Override
140 public void run() {
141 byte[] b = Session.toByteArray(currentSession.toProto());
142 String dir = mContext.getFilesDir().getAbsolutePath();
143 if (currentSession.getResult() != Session.SUCCESS) {
144 if (!mCollectBadTouches) {
145 return;
146 }
147 dir += "/bad_touches";
148 } else {
149 dir += "/good_touches";
150 }
151
152 File file = new File(dir);
153 file.mkdir();
154 File touch = new File(file, "trace_" + System.currentTimeMillis());
155
156 try {
157 new FileOutputStream(touch).write(b);
158 } catch (IOException e) {
159 throw new RuntimeException(e);
160 }
161 }
162 });
163 }
164
Blazej Magnowski72323322015-07-24 11:49:40 -0700165 @Override
166 public synchronized void onSensorChanged(SensorEvent event) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700167 if (mEnableCollector && mCurrentSession != null) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700168 mCurrentSession.addSensorEvent(event, System.nanoTime());
169 enforceTimeout();
170 }
171 }
172
173 private void enforceTimeout() {
174 if (mTimeoutActive) {
175 if (System.currentTimeMillis() - mCurrentSession.getStartTimestampMillis()
176 > TIMEOUT_MILLIS) {
177 onSessionEnd(Session.UNKNOWN);
178 if (DEBUG) {
179 Log.i(TAG, "Analytics timed out.");
180 }
181 }
182 }
183 }
184
185 @Override
186 public void onAccuracyChanged(Sensor sensor, int accuracy) {
187 }
188
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700189 public boolean isEnabled() {
190 return mEnableCollector;
Blazej Magnowski72323322015-07-24 11:49:40 -0700191 }
192
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700193 public void onScreenTurningOn() {
Blazej Magnowski72323322015-07-24 11:49:40 -0700194 if (sessionEntrypoint()) {
195 if (DEBUG) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700196 Log.d(TAG, "onScreenTurningOn");
Blazej Magnowski72323322015-07-24 11:49:40 -0700197 }
198 addEvent(PhoneEvent.ON_SCREEN_ON);
199 }
200 }
201
202 public void onScreenOnFromTouch() {
203 if (sessionEntrypoint()) {
204 if (DEBUG) {
205 Log.d(TAG, "onScreenOnFromTouch");
206 }
207 addEvent(PhoneEvent.ON_SCREEN_ON_FROM_TOUCH);
208 }
209 }
210
211 public void onScreenOff() {
212 if (DEBUG) {
213 Log.d(TAG, "onScreenOff");
214 }
215 addEvent(PhoneEvent.ON_SCREEN_OFF);
216 sessionExitpoint(Session.FAILURE);
217 }
218
219 public void onSucccessfulUnlock() {
220 if (DEBUG) {
221 Log.d(TAG, "onSuccessfulUnlock");
222 }
223 addEvent(PhoneEvent.ON_SUCCESSFUL_UNLOCK);
224 sessionExitpoint(Session.SUCCESS);
225 }
226
227 public void onBouncerShown() {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700228 if (DEBUG) {
229 Log.d(TAG, "onBouncerShown");
Blazej Magnowski72323322015-07-24 11:49:40 -0700230 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700231 addEvent(PhoneEvent.ON_BOUNCER_SHOWN);
Blazej Magnowski72323322015-07-24 11:49:40 -0700232 }
233
234 public void onBouncerHidden() {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700235 if (DEBUG) {
236 Log.d(TAG, "onBouncerHidden");
Blazej Magnowski72323322015-07-24 11:49:40 -0700237 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700238 addEvent(PhoneEvent.ON_BOUNCER_HIDDEN);
Blazej Magnowski72323322015-07-24 11:49:40 -0700239 }
240
241 public void onQsDown() {
242 if (DEBUG) {
243 Log.d(TAG, "onQsDown");
244 }
245 addEvent(PhoneEvent.ON_QS_DOWN);
246 }
247
248 public void setQsExpanded(boolean expanded) {
249 if (DEBUG) {
250 Log.d(TAG, "setQsExpanded = " + expanded);
251 }
252 if (expanded) {
253 addEvent(PhoneEvent.SET_QS_EXPANDED_TRUE);
254 } else {
255 addEvent(PhoneEvent.SET_QS_EXPANDED_FALSE);
256 }
257 }
258
259 public void onTrackingStarted() {
260 if (DEBUG) {
261 Log.d(TAG, "onTrackingStarted");
262 }
263 mTrackingStarted = true;
264 addEvent(PhoneEvent.ON_TRACKING_STARTED);
265 }
266
267 public void onTrackingStopped() {
268 if (mTrackingStarted) {
269 if (DEBUG) {
270 Log.d(TAG, "onTrackingStopped");
271 }
272 mTrackingStarted = false;
273 addEvent(PhoneEvent.ON_TRACKING_STOPPED);
274 }
275 }
276
277 public void onNotificationActive() {
278 if (DEBUG) {
279 Log.d(TAG, "onNotificationActive");
280 }
281 addEvent(PhoneEvent.ON_NOTIFICATION_ACTIVE);
282 }
283
284
285 public void onNotificationDoubleTap() {
286 if (DEBUG) {
287 Log.d(TAG, "onNotificationDoubleTap");
288 }
289 addEvent(PhoneEvent.ON_NOTIFICATION_DOUBLE_TAP);
290 }
291
292 public void setNotificationExpanded() {
293 if (DEBUG) {
294 Log.d(TAG, "setNotificationExpanded");
295 }
296 addEvent(PhoneEvent.SET_NOTIFICATION_EXPANDED);
297 }
298
299 public void onNotificatonStartDraggingDown() {
300 if (DEBUG) {
301 Log.d(TAG, "onNotificationStartDraggingDown");
302 }
303 addEvent(PhoneEvent.ON_NOTIFICATION_START_DRAGGING_DOWN);
304 }
305
306 public void onNotificatonStopDraggingDown() {
307 if (DEBUG) {
308 Log.d(TAG, "onNotificationStopDraggingDown");
309 }
310 addEvent(PhoneEvent.ON_NOTIFICATION_STOP_DRAGGING_DOWN);
311 }
312
313 public void onNotificationDismissed() {
314 if (DEBUG) {
315 Log.d(TAG, "onNotificationDismissed");
316 }
317 addEvent(PhoneEvent.ON_NOTIFICATION_DISMISSED);
318 }
319
320 public void onNotificatonStartDismissing() {
321 if (DEBUG) {
322 Log.d(TAG, "onNotificationStartDismissing");
323 }
324 addEvent(PhoneEvent.ON_NOTIFICATION_START_DISMISSING);
325 }
326
327 public void onNotificatonStopDismissing() {
328 if (DEBUG) {
329 Log.d(TAG, "onNotificationStopDismissing");
330 }
331 addEvent(PhoneEvent.ON_NOTIFICATION_STOP_DISMISSING);
332 }
333
334 public void onCameraOn() {
335 if (DEBUG) {
336 Log.d(TAG, "onCameraOn");
337 }
338 addEvent(PhoneEvent.ON_CAMERA_ON);
339 }
340
341 public void onLeftAffordanceOn() {
342 if (DEBUG) {
343 Log.d(TAG, "onLeftAffordanceOn");
344 }
345 addEvent(PhoneEvent.ON_LEFT_AFFORDANCE_ON);
346 }
347
348 public void onAffordanceSwipingStarted(boolean rightCorner) {
349 if (DEBUG) {
350 Log.d(TAG, "onAffordanceSwipingStarted");
351 }
352 mCornerSwiping = true;
353 if (rightCorner) {
354 addEvent(PhoneEvent.ON_RIGHT_AFFORDANCE_SWIPING_STARTED);
355 } else {
356 addEvent(PhoneEvent.ON_LEFT_AFFORDANCE_SWIPING_STARTED);
357 }
358 }
359
360 public void onAffordanceSwipingAborted() {
361 if (mCornerSwiping) {
362 if (DEBUG) {
363 Log.d(TAG, "onAffordanceSwipingAborted");
364 }
365 mCornerSwiping = false;
366 addEvent(PhoneEvent.ON_AFFORDANCE_SWIPING_ABORTED);
367 }
368 }
369
370 public void onUnlockHintStarted() {
371 if (DEBUG) {
372 Log.d(TAG, "onUnlockHintStarted");
373 }
374 addEvent(PhoneEvent.ON_UNLOCK_HINT_STARTED);
375 }
376
377 public void onCameraHintStarted() {
378 if (DEBUG) {
379 Log.d(TAG, "onCameraHintStarted");
380 }
381 addEvent(PhoneEvent.ON_CAMERA_HINT_STARTED);
382 }
383
384 public void onLeftAffordanceHintStarted() {
385 if (DEBUG) {
386 Log.d(TAG, "onLeftAffordanceHintStarted");
387 }
388 addEvent(PhoneEvent.ON_LEFT_AFFORDANCE_HINT_STARTED);
389 }
390
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700391 public void onTouchEvent(MotionEvent event, int width, int height) {
392 if (mCurrentSession != null) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700393 if (DEBUG) {
394 Log.v(TAG, "onTouchEvent(ev.action="
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700395 + MotionEvent.actionToString(event.getAction()) + ")");
Blazej Magnowski72323322015-07-24 11:49:40 -0700396 }
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700397 mCurrentSession.addMotionEvent(event);
Blazej Magnowski72323322015-07-24 11:49:40 -0700398 mCurrentSession.setTouchArea(width, height);
399 enforceTimeout();
400 }
401 }
402
403 private void addEvent(int eventType) {
Blazej Magnowski0e2ffbd2015-09-10 14:37:17 -0700404 if (mEnableCollector && mCurrentSession != null) {
Blazej Magnowski72323322015-07-24 11:49:40 -0700405 mCurrentSession.addPhoneEvent(eventType, System.nanoTime());
406 }
407 }
408}