Skip to content

Commit 05bb706

Browse files
author
David Gräff
committed
Add unobtrusive notifications. Improve/repair bug reporting. Update outlet overview only if necessary.
1 parent 127d8bb commit 05bb706

File tree

61 files changed

+1728
-1316
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1728
-1316
lines changed

.idea/workspace.xml

+455-558
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ android {
1616
defaultConfig {
1717
minSdkVersion 14
1818
targetSdkVersion 20
19-
versionCode 87
20-
versionName "5.9.2"
19+
versionCode 92
20+
versionName "5.12"
2121
applicationId appID
2222
testApplicationId "oly.netpowerctrl.tests"
2323
testInstrumentationRunner "android.test.InstrumentationTestRunner"

app/src/main/java/oly/netpowerctrl/anel/AnelEditDevice.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ public void onObserverJobFinished(List<Device> timeout_devices) {
5858
if (test_state != TestStates.TEST_REACHABLE)
5959
return;
6060

61+
// The device may not have a unique id and may be returned as timeout device.
62+
// Be careful with equalsByUniqueID which will crash on a device without an id!
6163
for (Device di : timeout_devices) {
62-
if (!di.equalsByUniqueID(device))
64+
if (di != device || !di.equalsByUniqueID(device))
6365
continue;
6466
test_state = TestStates.TEST_INIT;
6567
if (listener != null)
@@ -70,6 +72,9 @@ public void onObserverJobFinished(List<Device> timeout_devices) {
7072

7173
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
7274
public boolean startTest(Context context) {
75+
if (device == null)
76+
return false;
77+
7378
test_state = TestStates.TEST_REACHABLE;
7479

7580
if (wakeupPlugin(context))

app/src/main/java/oly/netpowerctrl/anel/AnelPlugin.java

+123-120
Large diffs are not rendered by default.

app/src/main/java/oly/netpowerctrl/anel/AnelPluginHttp.java

+16-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import oly.netpowerctrl.devices.DeviceConnection;
2020
import oly.netpowerctrl.devices.DeviceConnectionHTTP;
2121
import oly.netpowerctrl.listen_service.ListenService;
22+
import oly.netpowerctrl.main.App;
2223
import oly.netpowerctrl.network.HttpThreadPool;
2324
import oly.netpowerctrl.timer.Timer;
2425

@@ -33,11 +34,24 @@ public void httpResponse(DeviceConnection ci, boolean callback_success, String r
3334
final Device device = ci.getDevice();
3435
if (!callback_success) {
3536
ci.setNotReachable(response_message);
36-
AppData.getInstance().onDeviceUpdatedOtherThread(device);
37+
// Call onConfiguredDeviceUpdated to update device info.
38+
App.getMainThreadHandler().post(new Runnable() {
39+
@Override
40+
public void run() {
41+
AppData.getInstance().deviceCollection.updateNotReachable(App.instance, device);
42+
}
43+
});
3744
} else {
3845
String[] data = response_message.split(";");
3946
if (data.length < 10 || !data[0].startsWith("NET-")) {
4047
ci.setNotReachable(ListenService.getService().getString(R.string.error_packet_received));
48+
// Call onConfiguredDeviceUpdated to update device info.
49+
App.getMainThreadHandler().post(new Runnable() {
50+
@Override
51+
public void run() {
52+
AppData.getInstance().deviceCollection.updateNotReachable(App.instance, device);
53+
}
54+
});
4155
} else {
4256
// The name is the second ";" separated entry of the response_message.
4357
device.DeviceName = data[1].trim();
@@ -77,7 +91,7 @@ public void httpResponse(DeviceConnectionHTTP ci, boolean callback_success, Stri
7791
ci.setNotReachable(response_message);
7892
AppData.getInstance().onDeviceUpdatedOtherThread(device);
7993
} else
80-
HttpThreadPool.execute(HttpThreadPool.createHTTPRunner(ci, "strg.cfg", "", ci, false, receiveCtrlHtml));
94+
HttpThreadPool.execute(new HttpThreadPool.HTTPRunner<>(ci, "strg.cfg", "", ci, false, receiveCtrlHtml));
8195
}
8296
};
8397

app/src/main/java/oly/netpowerctrl/data/AppData.java

+27-7
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import oly.netpowerctrl.scenes.SceneCollection;
2828
import oly.netpowerctrl.scenes.SceneItem;
2929
import oly.netpowerctrl.timer.TimerController;
30-
import oly.netpowerctrl.utils.ShowToast;
30+
import oly.netpowerctrl.utils.notifications.InAppNotifications;
3131

3232
/**
3333
* Device Updates go into this object and are propagated to all observers and the device collection.
@@ -40,7 +40,7 @@ public class AppData {
4040
public static final DataQueryCompletedObserver observersDataQueryCompleted = new DataQueryCompletedObserver();
4141
public static final DataLoadedObserver observersOnDataLoaded = new DataLoadedObserver();
4242
public static final NewDeviceObserver observersNew = new NewDeviceObserver();
43-
private static final String TAG = AppData.class.getName();
43+
private static final String TAG = "AppData";
4444

4545
public final List<Device> newDevices = new ArrayList<>();
4646
final public DeviceCollection deviceCollection = new DeviceCollection();
@@ -127,7 +127,6 @@ public Set<Integer> getAllReceivePorts() {
127127
* @param deviceObserverBase The DeviceObserver
128128
*/
129129
public void removeUpdateDeviceState(DeviceObserverBase deviceObserverBase) {
130-
Log.w("DeviceQuery", "remove");
131130
updateDeviceStateList.remove(deviceObserverBase);
132131
}
133132

@@ -148,11 +147,23 @@ public void addUpdateDeviceState(DeviceObserverBase deviceObserverBase) {
148147
* @param source Source device that has been updated
149148
*/
150149
private void notifyDeviceQueries(Device source) {
150+
List<DeviceObserverBase> deviceObserverBaseList = new ArrayList<>();
151+
151152
Iterator<DeviceObserverBase> it = updateDeviceStateList.iterator();
152153
while (it.hasNext()) {
153154
// Return true if the DeviceQuery object has finished its task.
154-
if (it.next().notifyObservers(source))
155+
DeviceObserverBase deviceObserverBase = it.next();
156+
if (deviceObserverBase.notifyObservers(source)) {
155157
it.remove();
158+
deviceObserverBaseList.add(deviceObserverBase);
159+
}
160+
}
161+
162+
// Handle all finished device observers now. We have to split the finishing operation
163+
// and updateDeviceStateList iteration because finishWithTimeouts may cause that that this
164+
// method is called again and would lead to a concurrent access violation otherwise.
165+
for (DeviceObserverBase deviceObserverBase : deviceObserverBaseList) {
166+
deviceObserverBase.finishWithTimeouts();
156167
}
157168
}
158169

@@ -248,13 +259,16 @@ public void onDeviceErrorByName(Context context, String name, String errMessage)
248259
Iterator<DeviceObserverBase> it = updateDeviceStateList.iterator();
249260
while (it.hasNext()) {
250261
// Return true if the DeviceQuery object has finished its task.
251-
if (it.next().notifyObservers(name))
262+
DeviceObserverBase deviceObserverBase = it.next();
263+
if (deviceObserverBase.notifyObservers(name)) {
252264
it.remove();
265+
deviceObserverBase.finishWithTimeouts();
266+
}
253267
}
254268

255269
// error packet received
256270
String error = context.getString(R.string.error_packet_received) + ": " + errMessage;
257-
ShowToast.FromOtherThread(context, error);
271+
InAppNotifications.FromOtherThread(context, error);
258272
}
259273

260274
public int getReachableConfiguredDevices() {
@@ -354,6 +368,12 @@ public void execute(Scene scene, onExecutionFinished callback) {
354368
public void execute(final DevicePort port, final int command, final onExecutionFinished callback) {
355369
PluginInterface remote = port.device.getPluginInterface();
356370
if (remote != null) {
371+
// mark device as changed. Slaves (see below) will also enter this method and will be
372+
// marked as changed. This is necessary for this scenario: The slave is already in the target state
373+
// but will be marked as currently-executed (progressbar is visible). Because an update of the device
374+
// will not be propagated because nothing actually changed, this currently-executed state will
375+
// stay indefinitely. Therefore we mark all currently-executed devices as changed here.
376+
port.device.setHasChanged();
357377
remote.execute(port, command, callback);
358378

359379
// Support for slaves of an outlet.
@@ -369,7 +389,7 @@ else if (command == DevicePort.TOGGLE)
369389

370390
for (UUID slave_uuid : slaves) {
371391
DevicePort p = getInstance().findDevicePort(slave_uuid);
372-
if (p != null)
392+
if (p != null && p != port)
373393
execute(p, bValue ? DevicePort.ON : DevicePort.OFF, null);
374394
}
375395
}

app/src/main/java/oly/netpowerctrl/data/LoadStoreJSonData.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import oly.netpowerctrl.main.App;
1919
import oly.netpowerctrl.scenes.Scene;
2020
import oly.netpowerctrl.timer.Timer;
21-
import oly.netpowerctrl.utils.ShowToast;
21+
import oly.netpowerctrl.utils.notifications.InAppNotifications;
2222

2323

2424
/**
@@ -88,7 +88,7 @@ protected Boolean doInBackground(Void... voids) {
8888
LEGACY_READ_FROM_PREFERENCES a = new LEGACY_READ_FROM_PREFERENCES();
8989
a.read(appData);
9090
} catch (Exception e) {
91-
ShowToast.FromOtherThread(App.instance, R.string.error_import_legacy_data);
91+
InAppNotifications.FromOtherThread(App.instance, R.string.error_import_legacy_data);
9292
}
9393
}
9494
return null;
@@ -205,30 +205,30 @@ public void read(final AppData appData) {
205205
if (appData.sceneCollection.getItems().size() > 0)
206206
appData.sceneCollection.saveAll();
207207
} catch (IOException | InstantiationException | IllegalAccessException ignored) {
208-
ShowToast.FromOtherThread(context, R.string.error_reading_scenes);
208+
InAppNotifications.FromOtherThread(context, R.string.error_reading_scenes);
209209
}
210210
try {
211211
fromJSON(appData.deviceCollection.getItems(), JSONHelper.getReader(prefs.getString("devices", null)), Device.class);
212212
if (appData.deviceCollection.getItems().size() > 0)
213213
appData.deviceCollection.saveAll();
214214

215215
} catch (IOException | InstantiationException | IllegalAccessException ignored) {
216-
ShowToast.FromOtherThread(context, R.string.error_reading_devices);
216+
InAppNotifications.FromOtherThread(context, R.string.error_reading_devices);
217217
}
218218
try {
219219
fromJSON(appData.groupCollection.getItems(), JSONHelper.getReader(prefs.getString("groups", null)), Group.class);
220220
if (appData.groupCollection.getItems().size() > 0)
221221
appData.groupCollection.saveAll();
222222

223223
} catch (IOException | InstantiationException | IllegalAccessException ignored) {
224-
ShowToast.FromOtherThread(context, R.string.error_reading_groups);
224+
InAppNotifications.FromOtherThread(context, R.string.error_reading_groups);
225225
}
226226
try {
227227
fromJSON(appData.timerController.getItems(), JSONHelper.getReader(prefs.getString("alarms", null)), Timer.class);
228228
if (appData.timerController.getItems().size() > 0)
229229
appData.timerController.saveAll();
230230
} catch (IOException | InstantiationException | IllegalAccessException ignored) {
231-
ShowToast.FromOtherThread(context, R.string.error_reading_alarms);
231+
InAppNotifications.FromOtherThread(context, R.string.error_reading_alarms);
232232
}
233233
}
234234
}

app/src/main/java/oly/netpowerctrl/data/SharedPrefs.java

+34-24
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import android.content.Context;
55
import android.content.SharedPreferences;
66
import android.content.pm.PackageInfo;
7+
import android.content.pm.PackageManager;
78
import android.os.Bundle;
89
import android.os.Parcel;
910
import android.preference.PreferenceManager;
@@ -46,6 +47,27 @@ public static SharedPrefs getInstance() {
4647
return SingletonHolder.instance;
4748
}
4849

50+
public static String getVersionName(Context context) {
51+
try {
52+
Class cls = App.class;
53+
ComponentName comp = new ComponentName(context, cls);
54+
PackageInfo pinfo = context.getPackageManager().getPackageInfo(
55+
comp.getPackageName(), 0);
56+
return pinfo.versionName;
57+
} catch (android.content.pm.PackageManager.NameNotFoundException e) {
58+
return "";
59+
}
60+
}
61+
62+
public static int getVersionCode(Context context) {
63+
try {
64+
//noinspection ConstantConditions
65+
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
66+
} catch (PackageManager.NameNotFoundException e) {
67+
return 0;
68+
}
69+
}
70+
4971
public int getLastPreferenceVersion() {
5072

5173
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
@@ -248,38 +270,26 @@ public boolean isPreferenceNameLogEnergySaveMode(String name) {
248270
return name.equals("use_log_energy_saving_mode");
249271
}
250272

251-
String getVersionName(Context context) {
252-
try {
253-
Class cls = App.class;
254-
ComponentName comp = new ComponentName(context, cls);
255-
PackageInfo pinfo = context.getPackageManager().getPackageInfo(
256-
comp.getPackageName(), 0);
257-
return pinfo.versionName;
258-
} catch (android.content.pm.PackageManager.NameNotFoundException e) {
259-
return "";
260-
}
261-
}
262-
263273
/**
264-
* @return Return true if first run. All later calls will always return false.
274+
* @return Return true if this version is newer than a previous stored version. A
275+
* version is stored by calling {@see acceptUpdatedVersion}. False is returned if
276+
* this app runs the first time and for all subsequent starts with the same version.
265277
*/
266-
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
267-
public boolean showChangeLog() {
268-
278+
public boolean hasBeenUpdated() {
269279
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
270280
String lastStoredVersion = prefs.getString("lastVersion", null);
271281
String currentVersion = getVersionName(context);
272-
// Do not show changelog on first app opening (but on the next opening)
273-
if (lastStoredVersion == null && getFirstTab().equals("")) {
274-
prefs.edit().putString("lastVersion", "").apply();
282+
if (lastStoredVersion == null) {
283+
prefs.edit().putString("lastVersion", currentVersion).apply();
275284
return false;
276285
}
277-
// last version == current version: do not show changelog
278-
if (currentVersion.equals(lastStoredVersion))
279-
return false;
280-
// update last version and show change log
286+
return !currentVersion.equals(lastStoredVersion);
287+
}
288+
289+
public void acceptUpdatedVersion() {
290+
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
291+
String currentVersion = getVersionName(context);
281292
prefs.edit().putString("lastVersion", currentVersion).apply();
282-
return true;
283293
}
284294

285295
public boolean isNotification() {

app/src/main/java/oly/netpowerctrl/device_ports/DevicePortSourceConfigured.java

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public void setAutomaticUpdate(boolean enabled) {
6363
automaticUpdatesEnabled = enabled;
6464
if (!enabled) {
6565
AppData.getInstance().deviceCollection.unregisterObserver(this);
66+
AppData.getInstance().groupCollection.unregisterObserver(this);
6667
} else {
6768
// If no data has been loaded so far, wait for load action to be completed before
6869
// registering to deviceCollection changes.

app/src/main/java/oly/netpowerctrl/device_ports/DevicePortsBaseAdapter.java

+22-12
Original file line numberDiff line numberDiff line change
@@ -586,12 +586,21 @@ public void clear() {
586586
notifyDataSetChanged();
587587
}
588588

589-
public void removeAt(int position, boolean finalAction) {
590-
if (position == -1) return;
589+
/**
590+
* Remove item at position.
591+
*
592+
* @param position Index of item to remove
593+
* @param finalAction If this is a final action, group spans are recomputed.
594+
* @return Return amount of items that have been removed
595+
* (0, 1 or 2 if a group has been removed)
596+
*/
597+
public int removeAt(int position, boolean finalAction) {
598+
if (position == -1) return 0;
591599
AnimationController a = mAnimationWeakReference.get();
592600
if (a != null)
593601
a.beforeRemoval(position);
594602

603+
int removedItems = 1;
595604
mItems.remove(position);
596605

597606
/**
@@ -601,14 +610,19 @@ public void removeAt(int position, boolean finalAction) {
601610
*/
602611
for (int indexGroup = position - 1; indexGroup >= 0; --indexGroup) {
603612
DevicePortAdapterItem headerItem = mItems.get(indexGroup);
604-
if (headerItem.port == null) { // is header
605-
--headerItem.groupItems;
613+
if (headerItem.groupType() == DevicePortAdapterItem.groupTypeEnum.GROUP_TYPE) { // is header
614+
if (--headerItem.groupItems <= 0) {
615+
mItems.remove(indexGroup);
616+
++removedItems;
617+
}
606618
break;
607619
}
608620
}
609621

610622
if (finalAction)
611623
computeGroupSpans();
624+
625+
return removedItems;
612626
}
613627

614628
public void remove(DevicePort oi, boolean finalAction) {
@@ -626,16 +640,12 @@ public void markAllRemoved() {
626640
}
627641

628642
public void removeAllMarked(boolean finalAction) {
629-
int[] toBeRemoved = new int[mItems.size()];
630-
int lenToBeRemoved = 0;
631-
for (int index = 0; index < mItems.size(); ++index) {
643+
for (int index = mItems.size() - 1; index >= 0; ) {
632644
if (mItems.get(index).isMarkedRemoved()) {
633-
toBeRemoved[lenToBeRemoved++] = index;
634-
}
645+
index -= removeAt(index, false);
646+
} else
647+
--index;
635648
}
636-
// Remove now
637-
for (int i = lenToBeRemoved - 1; i >= 0; --i)
638-
removeAt(toBeRemoved[i], false);
639649

640650
if (finalAction)
641651
computeGroupSpans();

app/src/main/java/oly/netpowerctrl/device_ports/DevicePortsExecuteAdapter.java

+6
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ public DevicePortsExecuteAdapter(Context context, onListItemElementClicked mList
3636
source.updateNow();
3737
}
3838

39+
@Override
40+
public boolean isEnabled(int position) {
41+
DevicePort port = mItems.get(position).port;
42+
return port == null || port.device.getFirstReachableConnection() != null;
43+
}
44+
3945
@Override
4046
public View getView(int position, View convertView, ViewGroup parent) {
4147
DevicePortAdapterItem item = mItems.get(position);

0 commit comments

Comments
 (0)