Skip to content

Commit bd66db6

Browse files
committed
fix: state lost on orientation change in sensor activities
Fixes #2528
1 parent 640da48 commit bd66db6

30 files changed

+1338
-3017
lines changed

Diff for: app/src/main/java/io/pslab/communication/sensors/VL53L0X.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ private void performSingleRefCalibration(int vhvInitByte) throws Exception {
276276
i2c.write(ADDRESS, new int[]{0x00}, SYSRANGE_START);
277277
}
278278

279-
public int getRaw() throws Exception {
279+
public int getRaw() throws IOException {
280280
for (int[] regValPair : new int[][]{
281281
{0x80, 0x01},
282282
{0xFF, 0x01},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
package io.pslab.sensors;
2+
3+
import android.os.Bundle;
4+
import android.os.Handler;
5+
import android.os.HandlerThread;
6+
import android.os.Looper;
7+
import android.text.Editable;
8+
import android.util.Log;
9+
import android.view.MenuItem;
10+
import android.view.View;
11+
import android.widget.CheckBox;
12+
import android.widget.EditText;
13+
import android.widget.ImageButton;
14+
import android.widget.RelativeLayout;
15+
import android.widget.SeekBar;
16+
import android.widget.TextView;
17+
18+
import androidx.annotation.NonNull;
19+
import androidx.annotation.Nullable;
20+
import androidx.appcompat.app.ActionBar;
21+
import androidx.appcompat.app.AppCompatActivity;
22+
import androidx.appcompat.widget.Toolbar;
23+
24+
import java.util.Locale;
25+
26+
import io.pslab.R;
27+
import io.pslab.communication.ScienceLab;
28+
import io.pslab.others.ScienceLabCommon;
29+
30+
abstract class AbstractSensorActivity extends AppCompatActivity {
31+
private static final String TAG = AbstractSensorActivity.class.getSimpleName();
32+
33+
private static final String KEY_PLAY = TAG + "_play";
34+
private static final String KEY_RUN_INDEFINITELY = TAG + "_run_indefinitely";
35+
private static final String KEY_TIME_GAP = TAG + "_time_gap";
36+
private static final String KEY_FLAG = TAG + "_flag";
37+
private static final String KEY_START_TIME = TAG + "_start_time";
38+
private static final String KEY_COUNTER = TAG + "_counter";
39+
40+
private int counter;
41+
private ScienceLab scienceLab;
42+
private long startTime;
43+
private int flag;
44+
private boolean play = false;
45+
private boolean runIndefinitely = true;
46+
private int timeGap = 100;
47+
48+
private RelativeLayout sensorDock;
49+
private SeekBar timeGapSeekbar;
50+
private TextView timeGapLabel;
51+
private CheckBox indefiniteSamplesCheckBox;
52+
private EditText samplesEditBox;
53+
private ImageButton playPauseButton;
54+
55+
private HandlerThread handlerThread;
56+
private Handler handler;
57+
58+
@Override
59+
protected void onCreate(@Nullable Bundle savedInstanceState) {
60+
super.onCreate(savedInstanceState);
61+
setContentView(getLayoutResId());
62+
63+
if (savedInstanceState != null) {
64+
play = savedInstanceState.getBoolean(KEY_PLAY);
65+
runIndefinitely = savedInstanceState.getBoolean(KEY_RUN_INDEFINITELY);
66+
timeGap = savedInstanceState.getInt(KEY_TIME_GAP);
67+
flag = savedInstanceState.getInt(KEY_FLAG);
68+
startTime = savedInstanceState.getLong(KEY_START_TIME);
69+
counter = savedInstanceState.getInt(KEY_COUNTER);
70+
}
71+
72+
sensorDock = findViewById(R.id.sensor_control_dock_layout);
73+
indefiniteSamplesCheckBox = sensorDock.findViewById(R.id.checkBox_samples_sensor);
74+
timeGapSeekbar = sensorDock.findViewById(R.id.seekBar_timegap_sensor);
75+
timeGapLabel = sensorDock.findViewById(R.id.tv_timegap_label);
76+
samplesEditBox = sensorDock.findViewById(R.id.editBox_samples_sensors);
77+
playPauseButton = sensorDock.findViewById(R.id.imageButton_play_pause_sensor);
78+
79+
Toolbar toolbar = findViewById(R.id.toolbar);
80+
setSupportActionBar(toolbar);
81+
final ActionBar actionBar = getSupportActionBar();
82+
if (actionBar != null) {
83+
actionBar.setTitle(getTitleResId());
84+
actionBar.setDisplayHomeAsUpEnabled(true);
85+
actionBar.setDisplayShowHomeEnabled(true);
86+
}
87+
88+
setSensorDock();
89+
sensorDock.setVisibility(View.VISIBLE);
90+
91+
scienceLab = ScienceLabCommon.scienceLab;
92+
93+
handlerThread = new HandlerThread("MyHandlerThread");
94+
handlerThread.start();
95+
Looper looper = handlerThread.getLooper();
96+
handler = new Handler(looper);
97+
98+
if (play) {
99+
startTimerTask();
100+
}
101+
}
102+
103+
@Override
104+
protected void onDestroy() {
105+
play = false;
106+
handlerThread.quit();
107+
super.onDestroy();
108+
}
109+
110+
@Override
111+
protected void onSaveInstanceState(@NonNull Bundle outState) {
112+
super.onSaveInstanceState(outState);
113+
114+
outState.putBoolean(KEY_PLAY, play);
115+
outState.putBoolean(KEY_RUN_INDEFINITELY, runIndefinitely);
116+
outState.putInt(KEY_TIME_GAP, timeGap);
117+
outState.putInt(KEY_FLAG, flag);
118+
outState.putLong(KEY_START_TIME, startTime);
119+
}
120+
121+
@NonNull
122+
private Runnable getTimerTask() {
123+
124+
return () -> {
125+
if (play && scienceLab.isConnected() && shouldPlay()) {
126+
if (flag == 0) {
127+
startTime = System.currentTimeMillis();
128+
flag = 1;
129+
}
130+
SensorDataFetch sensorDataFetch = getSensorDataFetch();
131+
if (sensorDataFetch == null) {
132+
Log.w(TAG, "No SensorDataFetch!");
133+
} else {
134+
sensorDataFetch.execute();
135+
}
136+
if (play) {
137+
startTimerTask();
138+
}
139+
} else {
140+
setPlayButton(false);
141+
}
142+
};
143+
}
144+
145+
private void startTimerTask() {
146+
handler.postDelayed(getTimerTask(), timeGap);
147+
}
148+
149+
protected ScienceLab getScienceLab() {
150+
return scienceLab;
151+
}
152+
153+
private void setSensorDock() {
154+
final int step = 1;
155+
final int max = 1000;
156+
final int min = 100;
157+
158+
playPauseButton.setOnClickListener(v -> {
159+
if (play && scienceLab.isConnected()) {
160+
play = false;
161+
} else if (!scienceLab.isConnected()) {
162+
play = false;
163+
} else {
164+
play = true;
165+
startTimerTask();
166+
if (!indefiniteSamplesCheckBox.isChecked()) {
167+
Editable text = samplesEditBox.getText();
168+
counter = text.length() == 0 ? 0 : Integer.parseInt(text.toString());
169+
}
170+
}
171+
setPlayButton(play);
172+
});
173+
174+
setPlayButton(play);
175+
176+
sensorDock.setVisibility(View.VISIBLE);
177+
178+
indefiniteSamplesCheckBox.setChecked(runIndefinitely);
179+
samplesEditBox.setEnabled(!runIndefinitely);
180+
samplesEditBox.setText(String.valueOf(counter));
181+
indefiniteSamplesCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
182+
if (isChecked) {
183+
runIndefinitely = true;
184+
samplesEditBox.setEnabled(false);
185+
} else {
186+
runIndefinitely = false;
187+
samplesEditBox.setEnabled(true);
188+
}
189+
});
190+
191+
timeGapSeekbar.setMax((max - min) / step);
192+
timeGapSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
193+
@Override
194+
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
195+
timeGap = min + (progress * step);
196+
timeGapLabel.setText(
197+
String.format(
198+
Locale.getDefault(),
199+
"%d%s", timeGap, getString(R.string.unit_milliseconds)
200+
)
201+
);
202+
}
203+
204+
@Override
205+
public void onStartTrackingTouch(SeekBar seekBar) {
206+
// nothing to do here
207+
}
208+
209+
@Override
210+
public void onStopTrackingTouch(SeekBar seekBar) {
211+
// nothing to do here
212+
}
213+
});
214+
}
215+
216+
private void setPlayButton(boolean isPlaying) {
217+
playPauseButton.setImageResource(isPlaying ? R.drawable.circle_pause_button : R.drawable.circle_play_button);
218+
}
219+
220+
protected boolean shouldPlay() {
221+
if (play) {
222+
if (indefiniteSamplesCheckBox.isChecked())
223+
return true;
224+
else if (counter > 0) {
225+
counter--;
226+
return true;
227+
} else {
228+
play = false;
229+
return false;
230+
}
231+
} else {
232+
return false;
233+
}
234+
}
235+
236+
/**
237+
* Get time of first start of sensor data capture.
238+
*
239+
* @return time in ms
240+
*/
241+
protected long getStartTime() {
242+
return startTime;
243+
}
244+
245+
@Override
246+
public boolean onOptionsItemSelected(MenuItem item) {
247+
if (item.getItemId() == android.R.id.home) {
248+
finish();
249+
}
250+
return true;
251+
}
252+
253+
protected abstract SensorDataFetch getSensorDataFetch();
254+
255+
protected abstract int getLayoutResId();
256+
257+
protected abstract int getTitleResId();
258+
259+
protected abstract class SensorDataFetch {
260+
261+
private volatile boolean isSensorDataAcquired;
262+
263+
protected float getTimeElapsed() {
264+
return (System.currentTimeMillis() - getStartTime()) / 1000f;
265+
}
266+
267+
protected boolean isSensorDataAcquired() {
268+
return isSensorDataAcquired;
269+
}
270+
271+
protected void execute() {
272+
getSensorData();
273+
isSensorDataAcquired = true;
274+
updateUi();
275+
}
276+
277+
abstract void getSensorData();
278+
279+
abstract void updateUi();
280+
}
281+
}

0 commit comments

Comments
 (0)