1 /*
2  * Copyright (C) 2017 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 com.google.android.car.obd2app;
18 
19 import android.annotation.Nullable;
20 import android.content.Context;
21 import android.os.Environment;
22 import android.os.SystemClock;
23 import android.util.JsonWriter;
24 import android.util.Log;
25 import com.android.car.obd2.Obd2Connection;
26 import com.android.car.obd2.Obd2FreezeFrameGenerator;
27 import com.android.car.obd2.Obd2LiveFrameGenerator;
28 import com.android.car.obd2.connections.BluetoothConnection;
29 import java.io.File;
30 import java.io.FileOutputStream;
31 import java.io.IOException;
32 import java.io.OutputStreamWriter;
33 import java.util.Objects;
34 import java.util.TimerTask;
35 
36 public class Obd2CollectionTask extends TimerTask {
37     private final Obd2Connection mConnection;
38     private final Obd2LiveFrameGenerator mLiveFrameGenerator;
39     private final Obd2FreezeFrameGenerator mFreezeFrameGenerator;
40     private final StatusNotification mStatusNotification;
41     private final JsonWriter mJsonWriter;
42 
create( Context context, StatusNotification statusNotification, String deviceAddress)43     public static @Nullable Obd2CollectionTask create(
44             Context context, StatusNotification statusNotification, String deviceAddress) {
45         try {
46             return new Obd2CollectionTask(
47                     Objects.requireNonNull(context),
48                     Objects.requireNonNull(statusNotification),
49                     Objects.requireNonNull(deviceAddress));
50         } catch (IOException | InterruptedException | IllegalStateException e) {
51             Log.i(MainActivity.TAG, "Connection failed due to exception", e);
52             return null;
53         }
54     }
55 
56     @Override
cancel()57     public boolean cancel() {
58         synchronized (mJsonWriter) {
59             try {
60                 mJsonWriter.endArray();
61                 mJsonWriter.flush();
62                 mJsonWriter.close();
63             } catch (IOException e) {
64                 Log.w(MainActivity.TAG, "IOException during close", e);
65             }
66             return super.cancel();
67         }
68     }
69 
70     @Override
run()71     public void run() {
72         if (!mConnection.isConnected()) {
73             if (!mConnection.reconnect()) {
74                 mStatusNotification.notifyDisconnected();
75                 return;
76             }
77         }
78 
79         try {
80             synchronized (mJsonWriter) {
81                 mLiveFrameGenerator.generate(mJsonWriter);
82                 mFreezeFrameGenerator.generate(mJsonWriter);
83                 mJsonWriter.flush();
84             }
85             mStatusNotification.notifyDataCapture();
86         } catch (Exception e) {
87             mStatusNotification.notifyException(e);
88         }
89     }
90 
Obd2CollectionTask(Context context, StatusNotification statusNotification, String deviceAddress)91     Obd2CollectionTask(Context context, StatusNotification statusNotification, String deviceAddress)
92             throws IOException, InterruptedException {
93         if (!isExternalStorageWriteable())
94             throw new IOException("Cannot write data to external storage");
95         mStatusNotification = statusNotification;
96         BluetoothConnection bluetoothConnection = new BluetoothConnection(deviceAddress);
97         if (!bluetoothConnection.isConnected()) {
98             statusNotification.notifyConnectionFailed();
99             throw new IllegalStateException("Unable to connect to remote end.");
100         }
101         mConnection = new Obd2Connection(bluetoothConnection);
102         mLiveFrameGenerator = new Obd2LiveFrameGenerator(mConnection);
103         mFreezeFrameGenerator = new Obd2FreezeFrameGenerator(mConnection);
104         mJsonWriter =
105                 new JsonWriter(
106                         new OutputStreamWriter(
107                                 new FileOutputStream(getFilenameForStorage(context))));
108         mJsonWriter.beginArray();
109     }
110 
isExternalStorageWriteable()111     private static boolean isExternalStorageWriteable() {
112         String state = Environment.getExternalStorageState();
113         return (Environment.MEDIA_MOUNTED.equals(state));
114     }
115 
getFilenameForStorage(Context context)116     private static File getFilenameForStorage(Context context) {
117         String basename = String.format("obd2app.capture.%d", SystemClock.elapsedRealtimeNanos());
118         return new File(context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), basename);
119     }
120 }
121