From 405f519bfc5688bcacf202392a2afecaab42b462 Mon Sep 17 00:00:00 2001 From: Nils Schulte Date: Wed, 30 May 2018 17:15:31 +0200 Subject: [PATCH 1/3] initial commit --- .gitignore | 1 + mobile/build.gradle | 3 + mobile/src/full/AndroidManifest.xml | 4 + .../core/OpenHABGeofencingService.java | 221 ++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 mobile/src/full/java/org/openhab/habdroid/core/OpenHABGeofencingService.java diff --git a/.gitignore b/.gitignore index 4a20bb42fd..f0cda2e17b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .idea # Gradle files +gradle.properties .gradle **/build/ diff --git a/mobile/build.gradle b/mobile/build.gradle index ab165e102b..0c8a82b31c 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -105,6 +105,9 @@ dependencies { implementation 'com.github.loopj:android-smart-image-view:d09783b9508af5996361437a54cc234b662e4c78' implementation 'com.github.BigBadaboom:androidsvg:3511e136498da94018ef9fa438895984ea9b99db' implementation 'com.github.apl-devs:appintro:v4.2.2' + + // Location Services + fullImplementation 'com.google.android.gms:play-services-location:12.0.1' // Google Maps fullImplementation 'com.google.android.gms:play-services-maps:12.0.1' // GCM diff --git a/mobile/src/full/AndroidManifest.xml b/mobile/src/full/AndroidManifest.xml index 243313674c..346a5bd3b7 100644 --- a/mobile/src/full/AndroidManifest.xml +++ b/mobile/src/full/AndroidManifest.xml @@ -33,6 +33,10 @@ android:name="org.openhab.habdroid.core.GcmRegistrationService" android:exported="false" /> + + diff --git a/mobile/src/full/java/org/openhab/habdroid/core/OpenHABGeofencingService.java b/mobile/src/full/java/org/openhab/habdroid/core/OpenHABGeofencingService.java new file mode 100644 index 0000000000..38b2fb5054 --- /dev/null +++ b/mobile/src/full/java/org/openhab/habdroid/core/OpenHABGeofencingService.java @@ -0,0 +1,221 @@ +package org.openhab.habdroid.core; + +import android.app.IntentService; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; +import android.util.Log; +import android.widget.Toast; + +import com.google.android.gms.location.Geofence; +import com.google.android.gms.location.GeofencingEvent; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.openhab.habdroid.R; +import org.openhab.habdroid.core.connection.CloudConnection; +import org.openhab.habdroid.core.connection.Connection; +import org.openhab.habdroid.core.connection.ConnectionFactory; +import org.openhab.habdroid.core.connection.exception.ConnectionException; +import org.openhab.habdroid.util.AsyncHttpClient; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +import okhttp3.Headers; +import okhttp3.Request; + +public class OpenHABGeofencingService extends IntentService implements ConnectionFactory.UpdateListener { + private static final String TAG = OpenHABGeofencingService.class.getSimpleName(); + private Handler mHandler = new Handler(Looper.getMainLooper()); + + private Connection mConnection; + + public OpenHABGeofencingService() {super("OpenHABGeofencingService");} + + //private GeofencingClient mGeofencingClient; + + @Override + protected void onHandleIntent(@Nullable Intent intent) { + + ConnectionFactory.waitForInitialization(); + Connection connection = null; + + try { + connection = ConnectionFactory.getUsableConnection(); + } catch (ConnectionException e) { + Log.w(TAG, "Couldn't determine openHAB URL", e); + } + + showToast("Geofencing Intent"); + + Log.d(TAG, "Geofencing Intent"); + + // Create the NotificationChannel, but only on API 26+ because + // the NotificationChannel class is new and not in the support library + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = getString(R.string.geofencing_request_location_permission_dialog_title); + String description = getString(R.string.geofencing_request_location_permission); + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel channel = new NotificationChannel(getResources().getString(R.string.geofencing_request_location_permission), name, importance); + channel.setDescription(description); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + + String NotiString = ""; + GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); + if (geofencingEvent.hasError()) { + NotiString = "ERROR"; + } + // Get the transition type. + int geofenceTransition = geofencingEvent.getGeofenceTransition(); + + // Test that the reported transition was of interest. + if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) + NotiString = "GEOFENCE_TRANSITION_ENTER"; + else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) + NotiString = "GEOFENCE_TRANSITION_EXIT"; + + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext(), getResources().getString(R.string.geofencing_request_location_permission)) + .setSmallIcon(R.drawable.ic_notifications_black_24dp) + .setContentTitle("GEOFENCE") + .setContentText(NotiString) + .setPriority(NotificationCompat.PRIORITY_MAX); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext()); + + // notificationId is a unique int for each notification that you must define + int notificationId = 47; + notificationManager.notify(notificationId, mBuilder.build()); + + if (connection != null) { + //Util.sendItemCommand(mConnection.getAsyncHttpClient(),"rest/items/lamp_room_desk","ON"); + //sendVoiceCommand(connection.getSyncHttpClient(), voiceCommand); + } else { + //showToast(getString(R.string.error_couldnt_determine_openhab_url)); + } + } + + public static boolean loadGeofencesFromServer(final List geofenceList) { + + Connection connection = null; + + try { + connection = ConnectionFactory.getUsableConnection(); + } catch (ConnectionException e) { + Log.w(TAG, "Couldn't determine openHAB URL", e); + } + + if (connection != null) { + AsyncHttpClient client = connection.getAsyncHttpClient(); + + final HashMap headers = new HashMap<>(); + headers.put("Accept-Language", Locale.getDefault().getLanguage()); + + client.get("rest/items?type=Location&recursive", new AsyncHttpClient.StringResponseHandler() { + @Override + public void onFailure(Request request, int statusCode, Throwable error) { + Log.d(TAG, "Getting Items failed with "+statusCode); + } + @Override + public void onSuccess(String body, Headers headers) { + //JSONObject jsonObject = ; + JSONArray jsonArray; + try { + Log.d(TAG,body); + jsonArray = new JSONArray(body); + //jsonObject.getJSONArray(); + + Log.d(TAG,jsonArray.toString(4)); + + for(int i = 0;i Toast.makeText(this, text, Toast.LENGTH_LONG).show()); + } + + @Override + public void onAvailableConnectionChanged() { + + Log.d(TAG,"onAvailableConnectionChanged"); + Connection newConnection; + ConnectionException failureReason; + + try { + newConnection = ConnectionFactory.getUsableConnection(); + // failureReason = null; + } catch (ConnectionException e) { + newConnection = null; + //failureReason = e; + } + + + if (newConnection != null && newConnection == mConnection) { + return; + } + + + mConnection = newConnection; + //setupGeofences(); + } + + @Override + public void onCloudConnectionChanged(CloudConnection connection) { + + } +} From ce18e9dcdbb1aaeaa70947dd2f8009fb445a14e8 Mon Sep 17 00:00:00 2001 From: Nils Schulte Date: Fri, 1 Jun 2018 09:17:49 +0200 Subject: [PATCH 2/3] UI mainly working --- .../habdroid/core/GeofencingHelper.java | 17 + mobile/src/full/AndroidManifest.xml | 12 +- .../habdroid/core/GeofencingService.java | 478 ++++++++++++++++++ .../core/OpenHABGeofencingService.java | 221 -------- .../habdroid/ui/OpenHABGeofenceAdapter.java | 314 ++++++++++++ .../habdroid/ui/OpenHABGeofenceFragment.java | 227 +++++++++ .../full/res/layout/activity_geofencing.xml | 38 ++ .../full/res/menu/geofence_context_menu.xml | 13 + mobile/src/full/res/values/strings.xml | 8 + .../habdroid/model/OpenHABGeofence.java | 106 ++++ .../habdroid/ui/OpenHABMainActivity.java | 41 +- .../ui/activity/ContentController.java | 8 + .../org/openhab/habdroid/util/Constants.java | 10 +- .../java/org/openhab/habdroid/util/Util.java | 29 +- .../main/res/drawable/ic_add_clear_white.xml | 9 + .../layout/openhabgeofenceslist_fragment.xml | 28 + .../res/layout/openhabgeofenceslist_item.xml | 134 +++++ mobile/src/main/res/menu/left_drawer.xml | 4 + mobile/src/main/res/values/strings.xml | 1 + mobile/src/main/res/values/themes.xml | 10 + 20 files changed, 1477 insertions(+), 231 deletions(-) create mode 100644 mobile/src/foss/java/org/openhab/habdroid/core/GeofencingHelper.java create mode 100644 mobile/src/full/java/org/openhab/habdroid/core/GeofencingService.java delete mode 100644 mobile/src/full/java/org/openhab/habdroid/core/OpenHABGeofencingService.java create mode 100644 mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceAdapter.java create mode 100644 mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceFragment.java create mode 100644 mobile/src/full/res/layout/activity_geofencing.xml create mode 100644 mobile/src/full/res/menu/geofence_context_menu.xml create mode 100644 mobile/src/main/java/org/openhab/habdroid/model/OpenHABGeofence.java create mode 100644 mobile/src/main/res/drawable/ic_add_clear_white.xml create mode 100644 mobile/src/main/res/layout/openhabgeofenceslist_fragment.xml create mode 100644 mobile/src/main/res/layout/openhabgeofenceslist_item.xml diff --git a/mobile/src/foss/java/org/openhab/habdroid/core/GeofencingHelper.java b/mobile/src/foss/java/org/openhab/habdroid/core/GeofencingHelper.java new file mode 100644 index 0000000000..a245eab4ea --- /dev/null +++ b/mobile/src/foss/java/org/openhab/habdroid/core/GeofencingHelper.java @@ -0,0 +1,17 @@ +package org.openhab.habdroid.core; + +import android.app.Activity; + +public class GeofencingHelper { + private static final String TAG = GeofencingHelper.class.getSimpleName(); + + public static void setupGeofences(Activity activity) { + } + + public static void startSettingsActivity(Activity activity) { + } + + public static boolean isGeofenceFeatureAvailable() { + return false; + } +} diff --git a/mobile/src/full/AndroidManifest.xml b/mobile/src/full/AndroidManifest.xml index 346a5bd3b7..700275d504 100644 --- a/mobile/src/full/AndroidManifest.xml +++ b/mobile/src/full/AndroidManifest.xml @@ -2,6 +2,9 @@ + + + + + android:parentActivityName="org.openhab.habdroid.ui.OpenHABMainActivity" > + + @@ -34,7 +44,7 @@ android:exported="false" /> sGeofenceList; + private static List sGeofenceList; + + public static List getGeofences(Context context) { + return loadOpenHABGeofencesFromMemory(context); + } + + public static boolean isGeofenceFeatureAvailable() { + return true; + } + //asks for Location Permission when its not already granted + + public static class GeofenceNameNotUnique extends IllegalArgumentException { + + public GeofenceNameNotUnique() { + super("Name of geofence is not unique!"); + } + } + + /** + * Adds a new Geofence. + * Registers it on the google location API and saves list in memory. + * @param activity + * @param newOpenHABGeofence + * @throws GeofenceNameNotUnique + */ + public static void addGeofence(Activity activity, OpenHABGeofence newOpenHABGeofence) throws GeofenceNameNotUnique { + if (loadOpenHABGeofencesFromMemory(activity).contains(newOpenHABGeofence)) { + throw new GeofenceNameNotUnique();//Geofence name already registered + } + List newOpenHABGeofences = new ArrayList<>(1); + newOpenHABGeofences.add(newOpenHABGeofence); + registerGeofences(activity,newOpenHABGeofences); + } + + /** + * removes the geofences form memory and unregisters them from the google location API. + * @param activity + * @param geofencesForRemoval + */ + public static void removeGeofences(Activity activity, List geofencesForRemoval) { + if(geofencesForRemoval.isEmpty()) {return;} + GeofencingClient geofencingClient = LocationServices.getGeofencingClient(activity); + List activeOpenHABGeofences = loadOpenHABGeofencesFromMemory(activity); + activeOpenHABGeofences.removeAll(geofencesForRemoval); + ArrayList geofenceIDsForRemoval = new ArrayList<>(geofencesForRemoval.size()); + for(OpenHABGeofence geo:geofencesForRemoval) geofenceIDsForRemoval.add(geo.getName()); + geofencingClient.removeGeofences(geofenceIDsForRemoval);// google ID = OH2 name + storeGeofencesInMemory(activity,geofencesForRemoval); + broadcastUpdateGeofencesList(activity.getApplicationContext()); + } + + @SuppressLint("MissingPermission") + private static void registerGeofences(Activity activity, List newOpenHABGeofences) { + if (!hasLocationPermissions(activity)) { + requestLocationPermissionWithRationale(activity); + return; + } + GeofencingClient geofencingClient = LocationServices.getGeofencingClient(activity); + List googleGeofences = createGoogleGeofencesFromOpenHABGeofences(newOpenHABGeofences); + geofencingClient.addGeofences(getGeofencingRequest(googleGeofences), getGeofencePendingIntent(activity)) + .addOnSuccessListener(activity, aVoid -> { + Log.d(TAG, "geofences added"); + //Send a broadcast to the Fragment so it Updates the view and adds the new geofence. + broadcastUpdateGeofencesList(activity.getApplicationContext()); + //Save the Geofences so they don't get lost when this Service gets destroyed. + List allOpenHABGefences = loadOpenHABGeofencesFromMemory(activity); + allOpenHABGefences.addAll(newOpenHABGeofences); + storeGeofencesInMemory(activity,allOpenHABGefences); + }) + .addOnFailureListener(activity, e -> { + String errorMsg = "Failed to add geofences to Google location API"; + Log.e(TAG, errorMsg); + showToast(activity,errorMsg); + loadOpenHABGeofencesFromMemory(activity).removeAll(newOpenHABGeofences);//Maybe not necessary + }); + } + + /** + * Unregisters all OpenHABGeofences form the google location API. + * DOES NOT delete them form the memory or ui, + * so calling {@link GeofencingService#registerAllGeofences(Activity)} reverts the effect. + * @param context + */ + public static void unregisterAllGeofences(Context context) { + GeofencingClient geofencingClient = LocationServices.getGeofencingClient(context); + geofencingClient.removeGeofences(getGeofencePendingIntent(context)); + } + /** + * Registers all OpenHABGeofences that are stored in Momory at the google location API. + * Calling {@link GeofencingService#unregisterAllGeofences(Context)} reverts the effect. + * @param activity + */ + public static void registerAllGeofences(Activity activity) { + GeofencingClient geofencingClient = LocationServices.getGeofencingClient(activity); + registerGeofences(activity,loadOpenHABGeofencesFromMemory(activity)); + } + + /** + * Stores the List of OpenHABGeofences in the SharedPreferences + * @param context + * @param openHABGeofences the list to store + */ + private static void storeGeofencesInMemory(Context context,List openHABGeofences) { + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putString(context.getResources().getString(R.string.geofencing_preferences), + getJSONArrayFromGeofences(openHABGeofences).toString()).apply(); + } + + /** + * Loads the List of OpenHABGeofences from memory if it isn't already loaded. + * @param context + * @return the list + */ + private static List loadOpenHABGeofencesFromMemory(Context context) { + if (sGeofenceList != null) return sGeofenceList; + try { + JSONArray loadedOpenHABGeofencesJSONArray = new JSONArray( + PreferenceManager.getDefaultSharedPreferences(context) + .getString(context.getResources().getString(R.string.geofencing_preferences),"[]")); + sGeofenceList = loadGeofencesFromJSONArray(loadedOpenHABGeofencesJSONArray); + Log.d(TAG, "Geofences loaded from SharedPreferences:\n"+loadedOpenHABGeofencesJSONArray.toString()); + } catch (JSONException e) { + Log.e(TAG, "Couldn't load Geofences form SharedPreferences\n"+e.getLocalizedMessage()); + sGeofenceList = new ArrayList<>(0); + } + return sGeofenceList; + } + + private static boolean hasLocationPermissions(Context context){ + return (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED); + } + + private static void requestLocationPermissionWithRationale(Activity activity) { + if (ActivityCompat.shouldShowRequestPermissionRationale(activity, + Manifest.permission.ACCESS_FINE_LOCATION)) { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity) + .setTitle(R.string.permission_location__geofencing_request_dialog_title) + .setMessage(R.string.permission_location__geofencing_request); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + dialogBuilder.setOnDismissListener(dialog -> requestLocationPermission(activity)); + } else { + requestLocationPermission(activity); + } + dialogBuilder.show(); + } else { + // No explanation needed; request the permission + requestLocationPermission(activity); + } + } + + private static void requestLocationPermission(Activity activity) { + ActivityCompat.requestPermissions(activity,new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, Constants.REQUEST_LOCATION_REQUEST_CODE); + } + + private static void broadcastUpdateGeofencesList(Context context) { + Intent intent = new Intent(OpenHABGeofenceFragment.ACTION_UPDATE_VIEW); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + } + + private static JSONArray getJSONArrayFromGeofences(List openHABGeofences) { + ArrayList ar= new ArrayList<>(openHABGeofences.size()); + for (OpenHABGeofence j:openHABGeofences) { + try { + ar.add(j.toJSON()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + return new JSONArray(ar); + } + + private static List loadGeofencesFromJSONArray(JSONArray ar) { + int len = ar.length(); + List ret = new ArrayList(len); + for(int i = 0;i createGoogleGeofencesFromOpenHABGeofences(Collection openHABGeofences) { + ArrayList geofences = new ArrayList<>(openHABGeofences.size()); + for (OpenHABGeofence openHABGeofence:openHABGeofences) + geofences.add(new Geofence.Builder() + // Set the request ID of the geofence. This is a string to identify this geofence. + // We use the openHAB item name for that purpose as it also should be unique. + .setRequestId(openHABGeofence.getName()) + .setCircularRegion( + openHABGeofence.getLatitude(), + openHABGeofence.getLongitude(), + openHABGeofence.getRadius() + ) + .setExpirationDuration(Geofence.NEVER_EXPIRE) + .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | + Geofence.GEOFENCE_TRANSITION_EXIT) + .build()); + return geofences; + } + + /** + * + * @param geofences the list of geofences that should be added (has to not be empty!) + * @return + */ + private static GeofencingRequest getGeofencingRequest(List geofences) { + GeofencingRequest.Builder builder = new GeofencingRequest.Builder(); + builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER); + builder.addGeofences(geofences); + return builder.build(); + } + + private static PendingIntent getGeofencePendingIntent(Context context) { + // Reuse the PendingIntent if we already have it. + if (sGeofencePendingIntent != null) { + return sGeofencePendingIntent; + } + Intent intent = new Intent(context, GeofencingService.class); + // We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when + // calling addGeofences() and removeGeofences(). + sGeofencePendingIntent = PendingIntent.getService(context, 0, + intent, PendingIntent.FLAG_UPDATE_CURRENT); + return sGeofencePendingIntent; + } + + + private Handler mHandler = new Handler(Looper.getMainLooper()); + + private Connection mConnection; + + public GeofencingService() {super("GeofencingService"); + Log.d(TAG,"Geo Service Constructed ");} + + int notiCounter = 0; + int errorCounter = 4200; + //private GeofencingClient mGeofencingClient; + + @Override + protected void onHandleIntent(@Nullable Intent intent) { + ConnectionFactory.waitForInitialization(); + Connection connection = null; + + try { + connection = ConnectionFactory.getUsableConnection(); + } catch (ConnectionException e) { + Log.w(TAG, "Couldn't determine openHAB URL", e); + makeNotification(this.getApplicationContext(),"Geo Error","Couldn't determine openHAB URL",errorCounter++); + } + Log.d(TAG, "Geofencing Intent"); + + // Create the NotificationChannel, but only on API 26+ because + // the NotificationChannel class is new and not in the support library + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = getString(R.string.permission_location__geofencing_request_dialog_title); + String description = getString(R.string.permission_location__geofencing_request); + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel channel = new NotificationChannel(getResources().getString(R.string.permission_location__geofencing_request), name, importance); + channel.setDescription(description); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } + + GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); + // Get the transition type. + int geofenceTransition = geofencingEvent.getGeofenceTransition(); + + // Test that the reported transition was of interest. + if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) + makeNotification(getApplicationContext(),"Entered Fence",geofencingEvent.getTriggeringGeofences().get(0).getRequestId(),notiCounter++); + else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) + makeNotification(getApplicationContext(),"Exited Fence",geofencingEvent.getTriggeringGeofences().get(0).getRequestId(),notiCounter++); + + if (connection != null) { + //Util.sendItemCommand(mConnection.getAsyncHttpClient(),"rest/items/lamp_room_desk","ON"); + //sendVoiceCommand(connection.getSyncHttpClient(), voiceCommand); + } else { + //showToast(getString(R.string.error_couldnt_determine_openhab_url)); + } + } + + private static void makeNotification(Context c, String title, String text, int id) { + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(c, c.getResources().getString(R.string.permission_location__geofencing_request)) + .setSmallIcon(R.drawable.ic_notifications_black_24dp) + .setContentTitle(title) + .setContentText(text) + .setPriority(NotificationCompat.PRIORITY_MAX); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(c); + // notificationId is a unique int for each notification that you must define + notificationManager.notify(id, mBuilder.build()); + } + + public static boolean loadGeofencesFromServer(final List geofenceList) { + + Connection connection = null; + + try { + connection = ConnectionFactory.getUsableConnection(); + } catch (ConnectionException e) { + Log.w(TAG, "Couldn't determine openHAB URL", e); + } + + if (connection != null) { + AsyncHttpClient client = connection.getAsyncHttpClient(); + + final HashMap headers = new HashMap<>(); + headers.put("Accept-Language", Locale.getDefault().getLanguage()); + + client.get("rest/items?type=Location&recursive", new AsyncHttpClient.StringResponseHandler() { + @Override + public void onFailure(Request request, int statusCode, Throwable error) { + Log.d(TAG, "Getting Items failed with "+statusCode); + } + @Override + public void onSuccess(String body, Headers headers) { + //JSONObject jsonObject = ; + JSONArray jsonArray; + try { + Log.d(TAG,body); + jsonArray = new JSONArray(body); + //jsonObject.getJSONArray(); + + Log.d(TAG,jsonArray.toString(4)); + + for(int i = 0;i Toast.makeText(context, text, Toast.LENGTH_SHORT).show()); + } + + @Override + public void onAvailableConnectionChanged() { + + Log.d(TAG,"onAvailableConnectionChanged"); + Connection newConnection; + ConnectionException failureReason; + + try { + newConnection = ConnectionFactory.getUsableConnection(); + // failureReason = null; + } catch (ConnectionException e) { + newConnection = null; + //failureReason = e; + } + + + if (newConnection != null && newConnection == mConnection) { + return; + } + + + mConnection = newConnection; + //setupGeofences(); + } + + @Override + public void onCloudConnectionChanged(CloudConnection connection) { + + } +} diff --git a/mobile/src/full/java/org/openhab/habdroid/core/OpenHABGeofencingService.java b/mobile/src/full/java/org/openhab/habdroid/core/OpenHABGeofencingService.java deleted file mode 100644 index 38b2fb5054..0000000000 --- a/mobile/src/full/java/org/openhab/habdroid/core/OpenHABGeofencingService.java +++ /dev/null @@ -1,221 +0,0 @@ -package org.openhab.habdroid.core; - -import android.app.IntentService; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.support.annotation.Nullable; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.NotificationManagerCompat; -import android.util.Log; -import android.widget.Toast; - -import com.google.android.gms.location.Geofence; -import com.google.android.gms.location.GeofencingEvent; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import org.openhab.habdroid.R; -import org.openhab.habdroid.core.connection.CloudConnection; -import org.openhab.habdroid.core.connection.Connection; -import org.openhab.habdroid.core.connection.ConnectionFactory; -import org.openhab.habdroid.core.connection.exception.ConnectionException; -import org.openhab.habdroid.util.AsyncHttpClient; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; - -import okhttp3.Headers; -import okhttp3.Request; - -public class OpenHABGeofencingService extends IntentService implements ConnectionFactory.UpdateListener { - private static final String TAG = OpenHABGeofencingService.class.getSimpleName(); - private Handler mHandler = new Handler(Looper.getMainLooper()); - - private Connection mConnection; - - public OpenHABGeofencingService() {super("OpenHABGeofencingService");} - - //private GeofencingClient mGeofencingClient; - - @Override - protected void onHandleIntent(@Nullable Intent intent) { - - ConnectionFactory.waitForInitialization(); - Connection connection = null; - - try { - connection = ConnectionFactory.getUsableConnection(); - } catch (ConnectionException e) { - Log.w(TAG, "Couldn't determine openHAB URL", e); - } - - showToast("Geofencing Intent"); - - Log.d(TAG, "Geofencing Intent"); - - // Create the NotificationChannel, but only on API 26+ because - // the NotificationChannel class is new and not in the support library - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = getString(R.string.geofencing_request_location_permission_dialog_title); - String description = getString(R.string.geofencing_request_location_permission); - int importance = NotificationManager.IMPORTANCE_DEFAULT; - NotificationChannel channel = new NotificationChannel(getResources().getString(R.string.geofencing_request_location_permission), name, importance); - channel.setDescription(description); - // Register the channel with the system; you can't change the importance - // or other notification behaviors after this - NotificationManager notificationManager = getSystemService(NotificationManager.class); - notificationManager.createNotificationChannel(channel); - } - - String NotiString = ""; - GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); - if (geofencingEvent.hasError()) { - NotiString = "ERROR"; - } - // Get the transition type. - int geofenceTransition = geofencingEvent.getGeofenceTransition(); - - // Test that the reported transition was of interest. - if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) - NotiString = "GEOFENCE_TRANSITION_ENTER"; - else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) - NotiString = "GEOFENCE_TRANSITION_EXIT"; - - NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext(), getResources().getString(R.string.geofencing_request_location_permission)) - .setSmallIcon(R.drawable.ic_notifications_black_24dp) - .setContentTitle("GEOFENCE") - .setContentText(NotiString) - .setPriority(NotificationCompat.PRIORITY_MAX); - NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext()); - - // notificationId is a unique int for each notification that you must define - int notificationId = 47; - notificationManager.notify(notificationId, mBuilder.build()); - - if (connection != null) { - //Util.sendItemCommand(mConnection.getAsyncHttpClient(),"rest/items/lamp_room_desk","ON"); - //sendVoiceCommand(connection.getSyncHttpClient(), voiceCommand); - } else { - //showToast(getString(R.string.error_couldnt_determine_openhab_url)); - } - } - - public static boolean loadGeofencesFromServer(final List geofenceList) { - - Connection connection = null; - - try { - connection = ConnectionFactory.getUsableConnection(); - } catch (ConnectionException e) { - Log.w(TAG, "Couldn't determine openHAB URL", e); - } - - if (connection != null) { - AsyncHttpClient client = connection.getAsyncHttpClient(); - - final HashMap headers = new HashMap<>(); - headers.put("Accept-Language", Locale.getDefault().getLanguage()); - - client.get("rest/items?type=Location&recursive", new AsyncHttpClient.StringResponseHandler() { - @Override - public void onFailure(Request request, int statusCode, Throwable error) { - Log.d(TAG, "Getting Items failed with "+statusCode); - } - @Override - public void onSuccess(String body, Headers headers) { - //JSONObject jsonObject = ; - JSONArray jsonArray; - try { - Log.d(TAG,body); - jsonArray = new JSONArray(body); - //jsonObject.getJSONArray(); - - Log.d(TAG,jsonArray.toString(4)); - - for(int i = 0;i Toast.makeText(this, text, Toast.LENGTH_LONG).show()); - } - - @Override - public void onAvailableConnectionChanged() { - - Log.d(TAG,"onAvailableConnectionChanged"); - Connection newConnection; - ConnectionException failureReason; - - try { - newConnection = ConnectionFactory.getUsableConnection(); - // failureReason = null; - } catch (ConnectionException e) { - newConnection = null; - //failureReason = e; - } - - - if (newConnection != null && newConnection == mConnection) { - return; - } - - - mConnection = newConnection; - //setupGeofences(); - } - - @Override - public void onCloudConnectionChanged(CloudConnection connection) { - - } -} diff --git a/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceAdapter.java b/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceAdapter.java new file mode 100644 index 0000000000..5da152748f --- /dev/null +++ b/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceAdapter.java @@ -0,0 +1,314 @@ +package org.openhab.habdroid.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.support.annotation.NonNull; +import android.support.v7.widget.RecyclerView; +import android.util.TypedValue; +import android.view.ActionMode; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.google.android.gms.maps.CameraUpdateFactory; +import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.MapView; +import com.google.android.gms.maps.UiSettings; +import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.Marker; +import com.google.android.gms.maps.model.MarkerOptions; + +import org.openhab.habdroid.R; +import org.openhab.habdroid.core.GeofencingService; +import org.openhab.habdroid.model.OpenHABGeofence; +import org.openhab.habdroid.model.OpenHABItem; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +class OpenHABGeofenceAdapter extends RecyclerView.Adapter { + private static final String TAG = OpenHABGeofenceAdapter.class.getSimpleName(); + private final ArrayList mItems; + private final LayoutInflater mInflater; + private Activity mActivity; + + private ArrayList selectedItems; + + public OpenHABGeofenceAdapter(Activity activity, ArrayList items) { + super(); + selectedItems = new ArrayList<>(); + mItems = items; + mActivity = activity; + mInflater = LayoutInflater.from(activity); + } + + + @Override + public int getItemCount() { + return mItems.size(); + } + + @Override + public OpenHABGeofenceAdapter.GeofenceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new OpenHABGeofenceAdapter.GeofenceViewHolder(mInflater, parent); + } + + public void selectItem(OpenHABGeofenceAdapter.GeofenceViewHolder holder) { + selectedItems.add(holder); + TypedValue value = new TypedValue(); + mActivity.getTheme().resolveAttribute(R.attr.colorAccent, value, true); + holder.itemView.setBackgroundColor(value.data); + } + + public void unselectItem(OpenHABGeofenceAdapter.GeofenceViewHolder holder) { + selectedItems.remove(holder); + TypedValue value = new TypedValue(); + mActivity.getTheme().resolveAttribute(R.attr.backgroundColor, value, true); + holder.itemView.setBackgroundColor(value.data); + } + + @Override + public void onBindViewHolder(OpenHABGeofenceAdapter.GeofenceViewHolder holder, int position) { + OpenHABGeofence geofence = mItems.get(position); + holder.setGeofence(geofence); + holder.itemView.setOnLongClickListener(v -> { + if (mActionMode != null) { + return false; + } + + selectItem(holder); + // Start the CAB using the ActionMode.Callback defined above + mActionMode = mActivity.startActionMode(mActionModeCallback); + holder.itemView.setSelected(true); + holder.itemView.setActivated(true); + return true; + }); + holder.itemView.setOnClickListener(v -> { + if (mActionMode != null) { + if (selectedItems.contains(holder)) + unselectItem(holder); + else selectItem(holder); + if(selectedItems.size() == 0) + mActionMode.finish(); + } + }); + /*holder.mCreatedView.setText(DateUtils.getRelativeDateTimeString(mContext, + notification.createdTimestamp(), + DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0)); + holder.mMessageView.setText(notification.message()); + + if (notification.icon() != null) { + Connection conn = ConnectionFactory.getConnection(Connection.TYPE_CLOUD); + String iconUrl = String.format(Locale.US, "%simages/%s.png", + conn.getOpenHABUrl(), Uri.encode(notification.icon())); + holder.mIconView.setImageUrl(iconUrl, conn.getUsername(), conn.getPassword(), + R.drawable.ic_openhab_appicon_24dp); + } else { + }*/ + // holder.mIconView.setImageResource(R.drawable.ic_openhab_appicon_24dp); + } + + @Override + public void onViewRecycled(@NonNull GeofenceViewHolder holder) { + super.onViewRecycled(holder); + } + + private ActionMode mActionMode; + private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { + + // Called when the action mode is created; startActionMode() was called + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate a menu resource providing context menu items + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.geofence_context_menu, menu); + //menu.getItem(0).setIcon(R.drawable.ic_notifications_black_24dp); + return true; + } + + // Called each time the action mode is shown. Always called after onCreateActionMode, but + // may be called multiple times if the mode is invalidated. + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; // Return false if nothing is done + } + + // Called when the user selects a contextual menu item + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.id.delete: + //shareCurrentItem(); + List geosForRemoval = new ArrayList<>(selectedItems.size()); + for(OpenHABGeofenceAdapter.GeofenceViewHolder holder:selectedItems) { + geosForRemoval.add(holder.mGeofence); + } + GeofencingService.removeGeofences(mActivity,geosForRemoval); + mode.finish(); // Action picked, so close the CAB + return true; + default: + return false; + } + } + + // Called when the user exits the action mode + @Override + public void onDestroyActionMode(ActionMode mode) { + while (selectedItems.size() > 0) unselectItem(selectedItems.get(0)); + mActionMode = null; + } + }; + + + public static class GeofenceViewHolder extends RecyclerView.ViewHolder implements GoogleMap.OnMarkerDragListener { + private OpenHABGeofence mGeofence; + + private final MapView mMapView = itemView.findViewById(R.id.geofenceMapView); + private final TextView mLabelView = itemView.findViewById(R.id.geofencelabel); + private final TextView mNameView = itemView.findViewById(R.id.geofencename); + private final TextView mRadiusView = itemView.findViewById(R.id.geofenceradius); + private final TextView mCoordsView = itemView.findViewById(R.id.geofencecoordinates); + + private GoogleMap mMap; + private boolean mStarted; + + + public void setGeofence(OpenHABGeofence geofence) { + this.mGeofence = geofence; + mLabelView.setText(geofence.getLabel()); + mNameView .setText(geofence.getName()); + mRadiusView.setText(geofence.getRadius()+"m"); + mCoordsView.setText(geofence.getCoordinatesString()); + } + + public GeofenceViewHolder(LayoutInflater inflater, ViewGroup parent) { + super(inflater.inflate(R.layout.openhabgeofenceslist_item, parent, false)); + // itemView.set + mMapView.onCreate(null); + mMapView.getMapAsync(map -> { + mMap = map; + UiSettings settings = map.getUiSettings(); + settings.setAllGesturesEnabled(false); + settings.setMapToolbarEnabled(false); + map.setOnMarkerClickListener(marker -> { openPopup(); return true; }); + map.setOnMapClickListener(position -> openPopup()); + applyPositionAndLabel(map, 15.0f, false); + }); + start(); + } + + //@Override + public void start() { + if (!mStarted) { + mMapView.onStart(); + mMapView.onResume(); + mStarted = true; + } + } + + //@Override + public void stop() { + if (mStarted) { + mMapView.onPause(); + mMapView.onStop(); + mStarted = false; + } + } + + private void openPopup() { + final MapView mapView = new MapView(itemView.getContext()); + mapView.onCreate(null); + + AlertDialog dialog = new AlertDialog.Builder(itemView.getContext()) + .setView(mapView) + .setCancelable(true) + .setNegativeButton(R.string.close, null) + .create(); + + dialog.setOnDismissListener(dialogInterface -> { + mapView.onPause(); + mapView.onStop(); + mapView.onDestroy(); + }); + dialog.setCanceledOnTouchOutside(true); + dialog.show(); + + mapView.onStart(); + mapView.onResume(); + mapView.getMapAsync(map -> { + map.setOnMarkerDragListener(GeofenceViewHolder.this); + applyPositionAndLabel(map, 16.0f, true); + }); + } + + private void applyPositionAndLabel(GoogleMap map, float zoomLevel, boolean allowDrag) { + /*boolean canDragMarker = allowDrag && !mBoundItem.readOnly(); + if (!mBoundItem.members().isEmpty()) { + ArrayList positions = new ArrayList<>(); + for (OpenHABItem item : mBoundItem.members()) { + LatLng position = parseLocation(item.state()); + if (position != null) { + setMarker(map, position, item, item.label(), canDragMarker); + positions.add(position); + } + } + if (!positions.isEmpty()) { + LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); + for (LatLng position : positions) { + boundsBuilder.include(position); + } + map.moveCamera(CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), 0)); + float zoom = map.getCameraPosition().zoom; + if (zoom > zoomLevel) { + map.moveCamera(CameraUpdateFactory.zoomTo(zoomLevel)); + } + } + } else { + LatLng position = parseLocation(mBoundItem.state()); + if (position != null) { + setMarker(map, position, mBoundItem, mLabelView.getText(), canDragMarker); + map.moveCamera(CameraUpdateFactory.newLatLngZoom(position, zoomLevel)); + } + }*/ + LatLng position = null; + if (mGeofence != null) + position = new LatLng(mGeofence.getLatitude(),mGeofence.getLongitude()); + if (position != null) { + setMarker(map, position, null, mLabelView.getText(), false); + map.moveCamera(CameraUpdateFactory.newLatLngZoom(position, zoomLevel)); + } + } + + private static void setMarker(GoogleMap map, LatLng position, OpenHABItem item,CharSequence label, boolean canDrag) { + MarkerOptions marker = new MarkerOptions() + .draggable(canDrag) + .position(position) + .alpha(0.9f) + .title(label != null ? label.toString() : null); + map.addMarker(marker).setTag(item); + } + + @Override + public void onMarkerDragStart(Marker marker) { + + } + + @Override + public void onMarkerDrag(Marker marker) { + + } + + @Override + public void onMarkerDragEnd(Marker marker) { + String newState = String.format(Locale.US, "%f,%f", + marker.getPosition().latitude, marker.getPosition().longitude); + OpenHABItem item = (OpenHABItem) marker.getTag(); + + //Util.sendItemCommand(mConnection.getAsyncHttpClient(), item, newState); + } + } +} diff --git a/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceFragment.java b/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceFragment.java new file mode 100644 index 0000000000..2e528ad0bb --- /dev/null +++ b/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceFragment.java @@ -0,0 +1,227 @@ +package org.openhab.habdroid.ui; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.Fragment; +import android.support.v4.content.LocalBroadcastManager; +import android.support.v4.widget.SwipeRefreshLayout; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.openhab.habdroid.R; +import org.openhab.habdroid.core.GeofencingService; +import org.openhab.habdroid.model.OpenHABGeofence; +import org.openhab.habdroid.ui.widget.DividerItemDecoration; + +import java.util.ArrayList; + +import okhttp3.Call; + +public class OpenHABGeofenceFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { + + private static final String TAG = OpenHABGeofenceFragment.class.getSimpleName(); + public static final String ACTION_UPDATE_VIEW = "ACTION_UPDATE_VIEW"; + + private OpenHABMainActivity mActivity; + // keeps track of current request to cancel it in onPause + private Call mRequestHandle; + + private OpenHABGeofenceAdapter mGeofenceAdapter; + private ArrayList mGeofences; + + private RecyclerView mRecyclerView; + private SwipeRefreshLayout mSwipeLayout; + private FloatingActionButton mAddGeofenceFAB; + + public static OpenHABGeofenceFragment newInstance() { + OpenHABGeofenceFragment fragment = new OpenHABGeofenceFragment(); + Bundle args = new Bundle(); + fragment.setArguments(args); + return fragment; + } + + /** + * Mandatory empty constructor for the fragment manager to instantiate the + * fragment (e.g. upon screen orientation changes). + */ + public OpenHABGeofenceFragment() {} + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.d(TAG, "onCreate()"); + mGeofences = new ArrayList(); + } + + int i = 0; + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + //Log.i(TAG, "onCreateView"); + //Log.d(TAG, "isAdded = " + isAdded()); + View view = inflater.inflate(R.layout.openhabgeofenceslist_fragment, container, false); + mAddGeofenceFAB = view.findViewById(R.id.addGeofenceFAB); + mAddGeofenceFAB.setOnClickListener(v -> { + Log.i(TAG, "New Geofence"); + GeofencingService.addGeofence(getActivity(),new OpenHABGeofence(42,42,"42","fortytwo"+i++)); + }); + mSwipeLayout = view.findViewById(R.id.swipe_container); + mSwipeLayout.setOnRefreshListener(this); + mRecyclerView = view.findViewById(android.R.id.list); + + + return view; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + mActivity = (OpenHABMainActivity) getActivity(); + mGeofenceAdapter = new OpenHABGeofenceAdapter(mActivity, mGeofences); + + mRecyclerView.setLayoutManager(new LinearLayoutManager(mActivity)); + mRecyclerView.addItemDecoration(new DividerItemDecoration(mActivity)); + mRecyclerView.setAdapter(mGeofenceAdapter); + + //Log.d(TAG, "onActivityCreated()"); + //Log.d(TAG, "isAdded = " + isAdded()); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + Log.d(TAG, "onViewCreated"); + Log.d(TAG, "isAdded = " + isAdded()); + super.onViewCreated(view, savedInstanceState); + } + + BroadcastReceiver updateViewBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + loadGeofences(); + } + }; + + @Override + public void onResume() { + super.onResume(); + //Log.d(TAG, "onResume()"); + + LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).registerReceiver(updateViewBroadcastReceiver,new IntentFilter(ACTION_UPDATE_VIEW)); + loadGeofences(); + } + + @Override + public void onPause() { + super.onPause(); + //Log.d(TAG, "onPause()"); + LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).unregisterReceiver(updateViewBroadcastReceiver); + // Cancel request for notifications if there was any + if (mRequestHandle != null) { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + mRequestHandle.cancel(); + } + }); + thread.start(); + } + } + + @Override + public void onRefresh() { + //Log.d(TAG, "onRefresh()"); + loadGeofences(); + mSwipeLayout.setRefreshing(false); + } + + @Override + public void onDetach() { + super.onDetach(); + //Log.d(TAG, "onDetach()"); + mActivity = null; + } + + /*@Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuInflater inflater = getActivity().getMenuInflater(); + inflater.inflate(R.menu.geofence_context_menu, menu); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); + switch (item.getItemId()) { + case R.id.delete: + GeofencingService.removeGeofence(getActivity(),GeofencingService.getGeofences(getActivity()).get(info.position)); + return true; + case R.id.copy: + showToast("Feature not implemented yet"); + return true; + default: + return super.onContextItemSelected(item); + } + }*/ + + private void loadGeofences() { + mGeofences.clear(); + mGeofences.addAll(GeofencingService.getGeofences(getActivity())); + //mGeofences.add(new OpenHABGeofence(30.897957, -77.036560,100,"White House")); + mGeofenceAdapter.notifyDataSetChanged(); + +// mRequestHandle = conn.getAsyncHttpClient().get("api/v1/notifications?limit=20", +// new AsyncHttpClient.StringResponseHandler() { +// @Override +// public void onSuccess(String responseBody, Headers headers) { +// stopProgressIndicator(); +// Log.d(TAG, "Notifications request success"); +// try { +// JSONArray jsonArray = new JSONArray(responseBody); +// Log.d(TAG, jsonArray.toString()); +// mGeofences.clear(); +// for (int i = 0; i < jsonArray.length(); i++) { +// try { +// JSONObject sitemapJson = jsonArray.getJSONObject(i); +// mGeofences.add(OpenHABNotification.fromJson(sitemapJson)); +// } catch (JSONException e) { +// e.printStackTrace(); +// } +// } +// mGeofenceAdapter.notifyDataSetChanged(); +// } catch(JSONException e) { +// Log.d(TAG, e.getMessage(), e); +// } +// } +// +// @Override +// public void onFailure(Request request, int statusCode, Throwable error) { +// stopProgressIndicator(); +// Log.e(TAG, "Notifications request failure"); +// } +// }); + } + + private void stopProgressIndicator() { + if (mActivity != null) { + //Log.d(TAG, "Stop progress indicator"); + mActivity.setProgressIndicatorVisible(false); + } + } + + private void startProgressIndicator() { + if (mActivity != null) { + //Log.d(TAG, "Start progress indicator"); + mActivity.setProgressIndicatorVisible(true); + } + mSwipeLayout.setRefreshing(false); + } +} \ No newline at end of file diff --git a/mobile/src/full/res/layout/activity_geofencing.xml b/mobile/src/full/res/layout/activity_geofencing.xml new file mode 100644 index 0000000000..5ec9ead18e --- /dev/null +++ b/mobile/src/full/res/layout/activity_geofencing.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mobile/src/full/res/menu/geofence_context_menu.xml b/mobile/src/full/res/menu/geofence_context_menu.xml new file mode 100644 index 0000000000..266ded60a4 --- /dev/null +++ b/mobile/src/full/res/menu/geofence_context_menu.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/mobile/src/full/res/values/strings.xml b/mobile/src/full/res/values/strings.xml index 50e908da4b..63df13aa56 100644 --- a/mobile/src/full/res/values/strings.xml +++ b/mobile/src/full/res/values/strings.xml @@ -5,4 +5,12 @@ Device registration is in progress Device registration failed Device successfully registered with Google Cloud Messaging + + + "To use the geofencing feature, openHAB needs access to location data." + Location Permission + GEOFENCES_PREFERENCE + copy geofence + delete Geofence + \ No newline at end of file diff --git a/mobile/src/main/java/org/openhab/habdroid/model/OpenHABGeofence.java b/mobile/src/main/java/org/openhab/habdroid/model/OpenHABGeofence.java new file mode 100644 index 0000000000..93b101b15d --- /dev/null +++ b/mobile/src/main/java/org/openhab/habdroid/model/OpenHABGeofence.java @@ -0,0 +1,106 @@ +package org.openhab.habdroid.model; + +import org.json.JSONException; +import org.json.JSONObject; +import org.openhab.habdroid.util.Util; + +public class OpenHABGeofence { + + private String mLabel; + private String mName; + double mLatitude; + double mLongitude; + float mRadius;//in meters + + private OpenHABItem mSwitchItem; + + + public JSONObject toJSON() throws JSONException { + JSONObject ret = new JSONObject(); + ret.put("label",mLabel); + ret.put("name",mName); + ret.put("latitude",mLatitude); + ret.put("longitude",mLongitude); + ret.put("radius",mRadius); + return ret; + } + + @Override + public String toString() { + try { + return this.toJSON().toString(); + } catch (JSONException e) { + e.printStackTrace(); + } + return super.toString(); + } + + public static OpenHABGeofence fromJSON(JSONObject j) throws JSONException { + String label = j.getString("label"); + String name = j.getString("name"); + double lat= j.getDouble("latitude"); + double lon = j.getDouble("longitude"); + float radius= (float) j.getDouble("radius"); + return new OpenHABGeofence(lat,lon,radius,label,name); + } + + + public static final float DEFAULT_RADIUS = 100; + + public OpenHABGeofence(double latitude, double longitude, float radius, String label,String name) { + set(latitude,longitude,radius); + this.mLabel = label; + this.mName = name; + } + + public OpenHABGeofence(double latitude, double longitude, String label,String name) { + this(latitude,longitude,DEFAULT_RADIUS,label,name); + } + + private void set(double latitude, double longitude, float radius) { + this.mLatitude = latitude; + this.mLongitude = longitude; + this.mRadius = radius; + } + + public OpenHABItem getOpenHABItem() { + return mSwitchItem; + } + + public String getCoordinatesString() { + return Util.coordinatesStringFromValues(mLatitude,mLongitude); + } + + public String getLabel() { + return mLabel; + } + + public double getLongitude() { + return mLongitude; + } + + public double getLatitude() { + return mLatitude; + } + + public float getRadius() { + return mRadius; + } + + public String getName() { + return mName; + } + + /** + * To enable easy removal form lists. + * As names should be unique this is fine. + * @param obj to compare to + * @return true if the name is the same + */ + @Override + public boolean equals(Object obj) { + if(obj instanceof OpenHABGeofence) + return (((OpenHABGeofence) obj).mName.equals(mName)); + return super.equals(obj); + } +} diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/OpenHABMainActivity.java b/mobile/src/main/java/org/openhab/habdroid/ui/OpenHABMainActivity.java index 174db2d9fe..19129ce600 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/OpenHABMainActivity.java +++ b/mobile/src/main/java/org/openhab/habdroid/ui/OpenHABMainActivity.java @@ -12,13 +12,13 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.PendingIntent; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Configuration; import android.graphics.Bitmap; @@ -67,6 +67,7 @@ import org.json.JSONException; import org.openhab.habdroid.R; import org.openhab.habdroid.core.CloudMessagingHelper; +import org.openhab.habdroid.core.GeofencingService; import org.openhab.habdroid.core.OnUpdateBroadcastReceiver; import org.openhab.habdroid.core.OpenHABVoiceService; import org.openhab.habdroid.core.connection.CloudConnection; @@ -79,9 +80,9 @@ import org.openhab.habdroid.model.OpenHABLinkedPage; import org.openhab.habdroid.model.OpenHABSitemap; import org.openhab.habdroid.ui.activity.ContentController; +import org.openhab.habdroid.util.AsyncHttpClient; import org.openhab.habdroid.util.AsyncServiceResolver; import org.openhab.habdroid.util.Constants; -import org.openhab.habdroid.util.AsyncHttpClient; import org.openhab.habdroid.util.MyWebImage; import org.openhab.habdroid.util.Util; import org.w3c.dom.Document; @@ -403,8 +404,10 @@ public void onResume() { ConnectionFactory.addListener(this); MemorizingTrustManager.getInstance(this).bindDisplayActivity(this); + onAvailableConnectionChanged(); updateNotificationDrawerItem(); + updateGeofencingDrawerItem(); NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); if (nfcAdapter != null) { @@ -418,6 +421,13 @@ public void onResume() { checkFullscreen(); } + private void updateGeofencingDrawerItem() { + if (!GeofencingService.isGeofenceFeatureAvailable()){ + MenuItem geofencingItem = mDrawerMenu.findItem(R.id.geofencing); + geofencingItem.setVisible(ConnectionFactory.getConnection(Connection.TYPE_CLOUD) != null); + } + } + @Override protected void onPause() { super.onPause(); @@ -537,6 +547,25 @@ public void onStop() { } } + /** + * Used to get the result of the location-permission-request for geofencing + */ + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + switch (requestCode) { + case Constants.REQUEST_LOCATION_REQUEST_CODE: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + //Permission granted + } else { + //Permission not granted + } + return; + } + } + } + + public void triggerPageUpdate(String pageUrl, boolean forceReload) { mController.triggerPageUpdate(pageUrl, forceReload); } @@ -593,6 +622,9 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) { case R.id.notifications: openNotifications(); return true; + case R.id.geofencing: + openGeofences(); + return true; case R.id.settings: Intent settingsIntent = new Intent(OpenHABMainActivity.this, OpenHABPreferencesActivity.class); @@ -874,6 +906,11 @@ private void openNotifications() { mDrawerToggle.setDrawerIndicatorEnabled(false); } + private void openGeofences() { + mController.openGeofences(); + mDrawerToggle.setDrawerIndicatorEnabled(false); + } + private void openSitemap(OpenHABSitemap sitemap) { Log.i(TAG, "Opening sitemap " + sitemap + ", currently selected " + mSelectedSitemap); if (mSelectedSitemap != null && mSelectedSitemap.equals(sitemap)) { diff --git a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.java b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.java index bbd77ab0af..f2387ae134 100644 --- a/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.java +++ b/mobile/src/main/java/org/openhab/habdroid/ui/activity/ContentController.java @@ -41,6 +41,7 @@ import org.openhab.habdroid.model.OpenHABLinkedPage; import org.openhab.habdroid.model.OpenHABSitemap; import org.openhab.habdroid.model.OpenHABWidget; +import org.openhab.habdroid.ui.OpenHABGeofenceFragment; import org.openhab.habdroid.ui.OpenHABMainActivity; import org.openhab.habdroid.ui.OpenHABNotificationFragment; import org.openhab.habdroid.ui.OpenHABPreferencesActivity; @@ -291,6 +292,13 @@ public final void openNotifications() { showTemporaryPage(OpenHABNotificationFragment.newInstance()); } + /** + * Open a temporary page showing the geofences list + */ + public final void openGeofences() { + showTemporaryPage(OpenHABGeofenceFragment.newInstance()); + } + /** * Recreate all UI state * To be called from the activity's onCreate callback if the used controller changes diff --git a/mobile/src/main/java/org/openhab/habdroid/util/Constants.java b/mobile/src/main/java/org/openhab/habdroid/util/Constants.java index 52f6a2b78a..51440e4912 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/Constants.java +++ b/mobile/src/main/java/org/openhab/habdroid/util/Constants.java @@ -36,9 +36,11 @@ public class Constants { public static final String PREFERENCE_COMPAREABLEVERSION = "versionAsInt"; public static final String PREFERENCE_FIRST_START = "firstStart"; public static final String PREFERENCE_SWIPE_REFRESH_EXPLAINED = "swipToRefreshExplained"; - public static final String PREFERENCE_CHART_SCALING = "chartScalingFactor"; + public static final String PREFERENCE_CHART_SCALING = "chartScalingFactor"; - public static final String SUBSCREEN_LOCAL_CONNECTION = "default_openhab_local_connection"; - public static final String SUBSCREEN_REMOTE_CONNECTION = "default_openhab_remote_connection"; - public static final String SUBSCREEN_SSL_SETTINGS = "default_openhab_ssl"; + public static final String SUBSCREEN_LOCAL_CONNECTION = "default_openhab_local_connection"; + public static final String SUBSCREEN_REMOTE_CONNECTION = "default_openhab_remote_connection"; + public static final String SUBSCREEN_SSL_SETTINGS = "default_openhab_ssl"; + + public static final int REQUEST_LOCATION_REQUEST_CODE = 42; } \ No newline at end of file diff --git a/mobile/src/main/java/org/openhab/habdroid/util/Util.java b/mobile/src/main/java/org/openhab/habdroid/util/Util.java index fa14862522..acdf221ec6 100644 --- a/mobile/src/main/java/org/openhab/habdroid/util/Util.java +++ b/mobile/src/main/java/org/openhab/habdroid/util/Util.java @@ -13,6 +13,8 @@ import android.app.ActivityManager; import android.graphics.BitmapFactory; import android.net.Uri; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; import android.preference.PreferenceManager; import android.support.annotation.NonNull; import android.support.annotation.StyleRes; @@ -38,9 +40,6 @@ import okhttp3.Headers; import okhttp3.Request; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; - public class Util { private final static String TAG = Util.class.getSimpleName(); @@ -237,4 +236,28 @@ public static String obfuscateString(String string, int clearTextCharCount) { return string.substring(0, clearTextCharCount) + string.substring(clearTextCharCount).replaceAll(".", "*"); } + + /** + * returns a string with the latitude and longitude + * @param latitude the latitude + * @param longitude the longitude + * @return + */ + public static String coordinatesStringFromValues(double latitude,double longitude) { + String slat; //String lat = latitude<0?-latitude+"°S":latitude+"°N"; + String slon; //String lon = longitude<0?-longitude+"°W":longitude+"°E"; + if (latitude < 0) { + latitude = -latitude; + slat = "S"; + } else slat = "N"; + if (longitude < 0) { + longitude = -longitude; + slon = "W"; + } else slon = "E"; + int[] lat = {(int)latitude, (int)(latitude* 60) % 60,(int)(latitude* 3600) % 60}; + int[] lon = {(int)longitude,(int)(longitude*60) % 60,(int)(longitude*3600) % 60}; + slat = lat[0]+"°"+lat[1]+"'"+lat[2]+"\""+slat; + slon = lon[0]+"°"+lon[1]+"'"+lon[2]+"\""+slon; + return slat +" "+ slon; + } } diff --git a/mobile/src/main/res/drawable/ic_add_clear_white.xml b/mobile/src/main/res/drawable/ic_add_clear_white.xml new file mode 100644 index 0000000000..6e1d2c2ac2 --- /dev/null +++ b/mobile/src/main/res/drawable/ic_add_clear_white.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/mobile/src/main/res/layout/openhabgeofenceslist_fragment.xml b/mobile/src/main/res/layout/openhabgeofenceslist_fragment.xml new file mode 100644 index 0000000000..67f5c7e534 --- /dev/null +++ b/mobile/src/main/res/layout/openhabgeofenceslist_fragment.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/mobile/src/main/res/layout/openhabgeofenceslist_item.xml b/mobile/src/main/res/layout/openhabgeofenceslist_item.xml new file mode 100644 index 0000000000..ccf4679340 --- /dev/null +++ b/mobile/src/main/res/layout/openhabgeofenceslist_item.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/src/main/res/menu/left_drawer.xml b/mobile/src/main/res/menu/left_drawer.xml index e04b1f9584..651e7372e5 100644 --- a/mobile/src/main/res/menu/left_drawer.xml +++ b/mobile/src/main/res/menu/left_drawer.xml @@ -9,6 +9,10 @@ android:id="@+id/notifications" android:icon="@drawable/ic_notifications_black_24dp" android:title="@string/app_notifications" /> + Notifications Notification message + Geofencing Sitemaps diff --git a/mobile/src/main/res/values/themes.xml b/mobile/src/main/res/values/themes.xml index 2d68b032ab..523430f283 100644 --- a/mobile/src/main/res/values/themes.xml +++ b/mobile/src/main/res/values/themes.xml @@ -7,6 +7,8 @@ true false false + true + @style/ContextActionMode ?android:colorBackground ?android:colorBackground @@ -37,6 +39,8 @@ true false false + true + @style/ContextActionMode ?android:colorBackground ?android:colorBackground @@ -118,4 +122,10 @@ black + + + From 4071f90a2506b505872b51fc8fbbe8d9b0de208e Mon Sep 17 00:00:00 2001 From: Nils Schulte Date: Fri, 1 Jun 2018 15:24:26 +0200 Subject: [PATCH 3/3] Cleanup some of the comments --- .../habdroid/core/GeofencingService.java | 129 +++----------- .../habdroid/ui/OpenHABGeofenceAdapter.java | 59 ++----- .../habdroid/ui/OpenHABGeofenceFragment.java | 161 +++++++++++------- .../full/res/layout/activity_geofencing.xml | 38 ----- .../full/res/menu/geofence_context_menu.xml | 8 +- .../habdroid/model/OpenHABGeofence.java | 11 +- .../res/layout/openhabgeofences_dialog.xml | 44 +++++ .../res/layout/openhabgeofenceslist_item.xml | 8 +- mobile/src/main/res/values/strings.xml | 9 + 9 files changed, 200 insertions(+), 267 deletions(-) delete mode 100644 mobile/src/full/res/layout/activity_geofencing.xml create mode 100644 mobile/src/main/res/layout/openhabgeofences_dialog.xml diff --git a/mobile/src/full/java/org/openhab/habdroid/core/GeofencingService.java b/mobile/src/full/java/org/openhab/habdroid/core/GeofencingService.java index 336de71f50..1dec734c89 100644 --- a/mobile/src/full/java/org/openhab/habdroid/core/GeofencingService.java +++ b/mobile/src/full/java/org/openhab/habdroid/core/GeofencingService.java @@ -5,14 +5,11 @@ import android.app.Activity; import android.app.AlertDialog; import android.app.IntentService; -import android.app.NotificationChannel; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.preference.PreferenceManager; @@ -34,7 +31,6 @@ import org.json.JSONException; import org.json.JSONObject; import org.openhab.habdroid.R; -import org.openhab.habdroid.core.connection.CloudConnection; import org.openhab.habdroid.core.connection.Connection; import org.openhab.habdroid.core.connection.ConnectionFactory; import org.openhab.habdroid.core.connection.exception.ConnectionException; @@ -42,6 +38,7 @@ import org.openhab.habdroid.ui.OpenHABGeofenceFragment; import org.openhab.habdroid.util.AsyncHttpClient; import org.openhab.habdroid.util.Constants; +import org.openhab.habdroid.util.Util; import java.util.ArrayList; import java.util.Collection; @@ -52,10 +49,9 @@ import okhttp3.Headers; import okhttp3.Request; -public class GeofencingService extends IntentService implements ConnectionFactory.UpdateListener { +public class GeofencingService extends IntentService { private static final String TAG = GeofencingService.class.getSimpleName(); private static PendingIntent sGeofencePendingIntent; - //private static Collection sGeofenceList; private static List sGeofenceList; public static List getGeofences(Context context) { @@ -65,7 +61,6 @@ public static List getGeofences(Context context) { public static boolean isGeofenceFeatureAvailable() { return true; } - //asks for Location Permission when its not already granted public static class GeofenceNameNotUnique extends IllegalArgumentException { @@ -184,6 +179,20 @@ private static List loadOpenHABGeofencesFromMemory(Context cont return sGeofenceList; } + /** + * return the openhabgeofence with the google id (= name of geofence) or null if not found + * @param applicationContext + * @param requestId + */ + private OpenHABGeofence getGeofenceByID(Context applicationContext, String requestId) { + for(OpenHABGeofence geo:loadOpenHABGeofencesFromMemory(applicationContext)) { + if(geo.getName().equals(requestId)) + return geo; + } + return null; + + } + private static boolean hasLocationPermissions(Context context){ return (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED); } @@ -284,18 +293,9 @@ private static PendingIntent getGeofencePendingIntent(Context context) { return sGeofencePendingIntent; } - - private Handler mHandler = new Handler(Looper.getMainLooper()); - - private Connection mConnection; - public GeofencingService() {super("GeofencingService"); Log.d(TAG,"Geo Service Constructed ");} - int notiCounter = 0; - int errorCounter = 4200; - //private GeofencingClient mGeofencingClient; - @Override protected void onHandleIntent(@Nullable Intent intent) { ConnectionFactory.waitForInitialization(); @@ -305,42 +305,29 @@ protected void onHandleIntent(@Nullable Intent intent) { connection = ConnectionFactory.getUsableConnection(); } catch (ConnectionException e) { Log.w(TAG, "Couldn't determine openHAB URL", e); - makeNotification(this.getApplicationContext(),"Geo Error","Couldn't determine openHAB URL",errorCounter++); + makeNotification(this.getApplicationContext(),"Geo Error","Couldn't determine openHAB URL",notiCounter++); } Log.d(TAG, "Geofencing Intent"); - // Create the NotificationChannel, but only on API 26+ because - // the NotificationChannel class is new and not in the support library - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - CharSequence name = getString(R.string.permission_location__geofencing_request_dialog_title); - String description = getString(R.string.permission_location__geofencing_request); - int importance = NotificationManager.IMPORTANCE_DEFAULT; - NotificationChannel channel = new NotificationChannel(getResources().getString(R.string.permission_location__geofencing_request), name, importance); - channel.setDescription(description); - // Register the channel with the system; you can't change the importance - // or other notification behaviors after this - NotificationManager notificationManager = getSystemService(NotificationManager.class); - notificationManager.createNotificationChannel(channel); - } - GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); // Get the transition type. int geofenceTransition = geofencingEvent.getGeofenceTransition(); // Test that the reported transition was of interest. - if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) + if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) { makeNotification(getApplicationContext(),"Entered Fence",geofencingEvent.getTriggeringGeofences().get(0).getRequestId(),notiCounter++); - else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) - makeNotification(getApplicationContext(),"Exited Fence",geofencingEvent.getTriggeringGeofences().get(0).getRequestId(),notiCounter++); + Util.sendItemCommand(connection.getAsyncHttpClient(), + getGeofenceByID(getApplicationContext(),geofencingEvent.getTriggeringGeofences().get(0).getRequestId()).getOpenHABItem(), + "ON"); - if (connection != null) { - //Util.sendItemCommand(mConnection.getAsyncHttpClient(),"rest/items/lamp_room_desk","ON"); - //sendVoiceCommand(connection.getSyncHttpClient(), voiceCommand); - } else { - //showToast(getString(R.string.error_couldnt_determine_openhab_url)); + } + else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) { + makeNotification(getApplicationContext(),"Exited Fence",geofencingEvent.getTriggeringGeofences().get(0).getRequestId(),notiCounter++); } } + private static int notiCounter = 0; + //TODO change this to send the new geofence status to the openHAB server as a item private static void makeNotification(Context c, String title, String text, int id) { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(c, c.getResources().getString(R.string.permission_location__geofencing_request)) .setSmallIcon(R.drawable.ic_notifications_black_24dp) @@ -348,7 +335,6 @@ private static void makeNotification(Context c, String title, String text, int i .setContentText(text) .setPriority(NotificationCompat.PRIORITY_MAX); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(c); - // notificationId is a unique int for each notification that you must define notificationManager.notify(id, mBuilder.build()); } @@ -386,23 +372,7 @@ public void onSuccess(String body, Headers headers) { for(int i = 0;i Toast.makeText(context, text, Toast.LENGTH_SHORT).show()); } - - @Override - public void onAvailableConnectionChanged() { - - Log.d(TAG,"onAvailableConnectionChanged"); - Connection newConnection; - ConnectionException failureReason; - - try { - newConnection = ConnectionFactory.getUsableConnection(); - // failureReason = null; - } catch (ConnectionException e) { - newConnection = null; - //failureReason = e; - } - - - if (newConnection != null && newConnection == mConnection) { - return; - } - - - mConnection = newConnection; - //setupGeofences(); - } - - @Override - public void onCloudConnectionChanged(CloudConnection connection) { - - } } diff --git a/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceAdapter.java b/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceAdapter.java index 5da152748f..515adb75a9 100644 --- a/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceAdapter.java +++ b/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceAdapter.java @@ -62,13 +62,21 @@ public void selectItem(OpenHABGeofenceAdapter.GeofenceViewHolder holder) { TypedValue value = new TypedValue(); mActivity.getTheme().resolveAttribute(R.attr.colorAccent, value, true); holder.itemView.setBackgroundColor(value.data); + if(selectedItems.size() > 1) { + mActionMode.getMenu().findItem(R.id.copy).setVisible(false); + } + } - public void unselectItem(OpenHABGeofenceAdapter.GeofenceViewHolder holder) { + public void deselectItem(OpenHABGeofenceAdapter.GeofenceViewHolder holder) { selectedItems.remove(holder); TypedValue value = new TypedValue(); mActivity.getTheme().resolveAttribute(R.attr.backgroundColor, value, true); holder.itemView.setBackgroundColor(value.data); + + if(!(selectedItems.size() > 1)) { + mActionMode.getMenu().findItem(R.id.copy).setVisible(true); + } } @Override @@ -90,26 +98,12 @@ public void onBindViewHolder(OpenHABGeofenceAdapter.GeofenceViewHolder holder, i holder.itemView.setOnClickListener(v -> { if (mActionMode != null) { if (selectedItems.contains(holder)) - unselectItem(holder); + deselectItem(holder); else selectItem(holder); if(selectedItems.size() == 0) mActionMode.finish(); } }); - /*holder.mCreatedView.setText(DateUtils.getRelativeDateTimeString(mContext, - notification.createdTimestamp(), - DateUtils.MINUTE_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0)); - holder.mMessageView.setText(notification.message()); - - if (notification.icon() != null) { - Connection conn = ConnectionFactory.getConnection(Connection.TYPE_CLOUD); - String iconUrl = String.format(Locale.US, "%simages/%s.png", - conn.getOpenHABUrl(), Uri.encode(notification.icon())); - holder.mIconView.setImageUrl(iconUrl, conn.getUsername(), conn.getPassword(), - R.drawable.ic_openhab_appicon_24dp); - } else { - }*/ - // holder.mIconView.setImageResource(R.drawable.ic_openhab_appicon_24dp); } @Override @@ -158,7 +152,7 @@ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { // Called when the user exits the action mode @Override public void onDestroyActionMode(ActionMode mode) { - while (selectedItems.size() > 0) unselectItem(selectedItems.get(0)); + while (selectedItems.size() > 0) deselectItem(selectedItems.get(0)); mActionMode = null; } }; @@ -246,34 +240,7 @@ private void openPopup() { } private void applyPositionAndLabel(GoogleMap map, float zoomLevel, boolean allowDrag) { - /*boolean canDragMarker = allowDrag && !mBoundItem.readOnly(); - if (!mBoundItem.members().isEmpty()) { - ArrayList positions = new ArrayList<>(); - for (OpenHABItem item : mBoundItem.members()) { - LatLng position = parseLocation(item.state()); - if (position != null) { - setMarker(map, position, item, item.label(), canDragMarker); - positions.add(position); - } - } - if (!positions.isEmpty()) { - LatLngBounds.Builder boundsBuilder = new LatLngBounds.Builder(); - for (LatLng position : positions) { - boundsBuilder.include(position); - } - map.moveCamera(CameraUpdateFactory.newLatLngBounds(boundsBuilder.build(), 0)); - float zoom = map.getCameraPosition().zoom; - if (zoom > zoomLevel) { - map.moveCamera(CameraUpdateFactory.zoomTo(zoomLevel)); - } - } - } else { - LatLng position = parseLocation(mBoundItem.state()); - if (position != null) { - setMarker(map, position, mBoundItem, mLabelView.getText(), canDragMarker); - map.moveCamera(CameraUpdateFactory.newLatLngZoom(position, zoomLevel)); - } - }*/ + LatLng position = null; if (mGeofence != null) position = new LatLng(mGeofence.getLatitude(),mGeofence.getLongitude()); @@ -307,8 +274,6 @@ public void onMarkerDragEnd(Marker marker) { String newState = String.format(Locale.US, "%f,%f", marker.getPosition().latitude, marker.getPosition().longitude); OpenHABItem item = (OpenHABItem) marker.getTag(); - - //Util.sendItemCommand(mConnection.getAsyncHttpClient(), item, newState); } } } diff --git a/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceFragment.java b/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceFragment.java index 2e528ad0bb..fea012a4b1 100644 --- a/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceFragment.java +++ b/mobile/src/full/java/org/openhab/habdroid/ui/OpenHABGeofenceFragment.java @@ -1,20 +1,28 @@ package org.openhab.habdroid.ui; +import android.app.AlertDialog; +import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.support.v4.content.LocalBroadcastManager; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; +import android.text.InputFilter; +import android.text.Spanned; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.EditText; import org.openhab.habdroid.R; import org.openhab.habdroid.core.GeofencingService; @@ -22,6 +30,8 @@ import org.openhab.habdroid.ui.widget.DividerItemDecoration; import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import okhttp3.Call; @@ -72,7 +82,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mAddGeofenceFAB = view.findViewById(R.id.addGeofenceFAB); mAddGeofenceFAB.setOnClickListener(v -> { Log.i(TAG, "New Geofence"); - GeofencingService.addGeofence(getActivity(),new OpenHABGeofence(42,42,"42","fortytwo"+i++)); + FragmentManager fm = getActivity().getSupportFragmentManager(); + NewGeofenceDialogFragment newGeofenceDialogFragment = NewGeofenceDialogFragment.newInstance(null); + newGeofenceDialogFragment.show(fm, getResources().getString(R.string.geofence_dialog_new)); + //GeofencingService.addGeofence(getActivity(),new OpenHABGeofence(42,42,"42","fortytwo"+i++)); }); mSwipeLayout = view.findViewById(R.id.swipe_container); mSwipeLayout.setOnRefreshListener(this); @@ -113,8 +126,6 @@ public void onReceive(Context context, Intent intent) { @Override public void onResume() { super.onResume(); - //Log.d(TAG, "onResume()"); - LocalBroadcastManager.getInstance(getActivity().getApplicationContext()).registerReceiver(updateViewBroadcastReceiver,new IntentFilter(ACTION_UPDATE_VIEW)); loadGeofences(); } @@ -150,78 +161,100 @@ public void onDetach() { mActivity = null; } - /*@Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - MenuInflater inflater = getActivity().getMenuInflater(); - inflater.inflate(R.menu.geofence_context_menu, menu); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); - switch (item.getItemId()) { - case R.id.delete: - GeofencingService.removeGeofence(getActivity(),GeofencingService.getGeofences(getActivity()).get(info.position)); - return true; - case R.id.copy: - showToast("Feature not implemented yet"); - return true; - default: - return super.onContextItemSelected(item); - } - }*/ - private void loadGeofences() { mGeofences.clear(); mGeofences.addAll(GeofencingService.getGeofences(getActivity())); //mGeofences.add(new OpenHABGeofence(30.897957, -77.036560,100,"White House")); mGeofenceAdapter.notifyDataSetChanged(); -// mRequestHandle = conn.getAsyncHttpClient().get("api/v1/notifications?limit=20", -// new AsyncHttpClient.StringResponseHandler() { -// @Override -// public void onSuccess(String responseBody, Headers headers) { -// stopProgressIndicator(); -// Log.d(TAG, "Notifications request success"); -// try { -// JSONArray jsonArray = new JSONArray(responseBody); -// Log.d(TAG, jsonArray.toString()); -// mGeofences.clear(); -// for (int i = 0; i < jsonArray.length(); i++) { -// try { -// JSONObject sitemapJson = jsonArray.getJSONObject(i); -// mGeofences.add(OpenHABNotification.fromJson(sitemapJson)); -// } catch (JSONException e) { -// e.printStackTrace(); -// } -// } -// mGeofenceAdapter.notifyDataSetChanged(); -// } catch(JSONException e) { -// Log.d(TAG, e.getMessage(), e); -// } -// } -// -// @Override -// public void onFailure(Request request, int statusCode, Throwable error) { -// stopProgressIndicator(); -// Log.e(TAG, "Notifications request failure"); -// } -// }); + } - private void stopProgressIndicator() { - if (mActivity != null) { - //Log.d(TAG, "Stop progress indicator"); - mActivity.setProgressIndicatorVisible(false); + // This is called when the dialog is completed and the results have been passed + public static class RegexInputFilter implements InputFilter { + private Pattern mPattern; + private static final String CLASS_NAME = RegexInputFilter.class.getSimpleName(); + /** + * Convenience constructor, builds Pattern object from a String + * @param pattern Regex string to build pattern from. + */ + public RegexInputFilter(String pattern) { + this(Pattern.compile(pattern)); + } + public RegexInputFilter(Pattern pattern) { + if (pattern == null) { + throw new IllegalArgumentException(CLASS_NAME + " requires a regex."); + } + + mPattern = pattern; + } + @Override + public CharSequence filter(CharSequence source, int start, int end, + Spanned dest, int dstart, int dend) { + + Matcher matcher = mPattern.matcher(source); + if (!matcher.matches()) { + return ""; + } + + return null; } } + public static class NewGeofenceDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + + - private void startProgressIndicator() { - if (mActivity != null) { - //Log.d(TAG, "Start progress indicator"); - mActivity.setProgressIndicatorVisible(true); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + // Get the layout inflater + LayoutInflater inflater = getActivity().getLayoutInflater(); + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + View view = inflater.inflate(R.layout.openhabgeofences_dialog, null); + EditText label = view.findViewById(R.id.geofencelabel); + EditText name = view.findViewById(R.id.geofencename); + name.setFilters(new InputFilter[]{ + new RegexInputFilter("[A-Za-z_\\-0-9]*")}); + EditText radius = view.findViewById(R.id.geofenceradius); + + if (savedInstanceState != null) { + label.setText(savedInstanceState.getCharSequence("label","")); + name.setText(savedInstanceState.getCharSequence("name","")); + radius.setText(savedInstanceState.getCharSequence("radius","100")); + } + builder.setView(view) + // Add action buttons + .setPositiveButton(R.string.geofence_dialog_create, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + OpenHABGeofence geo = null; + try { + geo = new OpenHABGeofence( + 42, + 42, + Float.valueOf(radius.getText().toString()), + name.getText().toString(), + label.getText().toString()); + } catch (NumberFormatException e) { + } + if (geo != null) + GeofencingService.addGeofence(getActivity(),geo); + } + }) + .setNegativeButton(R.string.geofence_dialog_cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + NewGeofenceDialogFragment.this.getDialog().cancel(); + } + }); + return builder.create(); + } + + public static NewGeofenceDialogFragment newInstance(Bundle args) { + NewGeofenceDialogFragment frag = new NewGeofenceDialogFragment(); + + frag.setArguments(args); + return frag; } - mSwipeLayout.setRefreshing(false); } } \ No newline at end of file diff --git a/mobile/src/full/res/layout/activity_geofencing.xml b/mobile/src/full/res/layout/activity_geofencing.xml deleted file mode 100644 index 5ec9ead18e..0000000000 --- a/mobile/src/full/res/layout/activity_geofencing.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/mobile/src/full/res/menu/geofence_context_menu.xml b/mobile/src/full/res/menu/geofence_context_menu.xml index 266ded60a4..4f1cc3e355 100644 --- a/mobile/src/full/res/menu/geofence_context_menu.xml +++ b/mobile/src/full/res/menu/geofence_context_menu.xml @@ -2,12 +2,12 @@ - + \ No newline at end of file diff --git a/mobile/src/main/java/org/openhab/habdroid/model/OpenHABGeofence.java b/mobile/src/main/java/org/openhab/habdroid/model/OpenHABGeofence.java index 93b101b15d..e04ae30b90 100644 --- a/mobile/src/main/java/org/openhab/habdroid/model/OpenHABGeofence.java +++ b/mobile/src/main/java/org/openhab/habdroid/model/OpenHABGeofence.java @@ -44,17 +44,12 @@ public static OpenHABGeofence fromJSON(JSONObject j) throws JSONException { return new OpenHABGeofence(lat,lon,radius,label,name); } - - public static final float DEFAULT_RADIUS = 100; - - public OpenHABGeofence(double latitude, double longitude, float radius, String label,String name) { + public OpenHABGeofence(double latitude, double longitude, float radius,String name,String label) { set(latitude,longitude,radius); - this.mLabel = label; this.mName = name; - } + this.mLabel = label; - public OpenHABGeofence(double latitude, double longitude, String label,String name) { - this(latitude,longitude,DEFAULT_RADIUS,label,name); + //TODO create the openHABItem maybe ? } private void set(double latitude, double longitude, float radius) { diff --git a/mobile/src/main/res/layout/openhabgeofences_dialog.xml b/mobile/src/main/res/layout/openhabgeofences_dialog.xml new file mode 100644 index 0000000000..c5f1c3af68 --- /dev/null +++ b/mobile/src/main/res/layout/openhabgeofences_dialog.xml @@ -0,0 +1,44 @@ + + + + + + + \ No newline at end of file diff --git a/mobile/src/main/res/layout/openhabgeofenceslist_item.xml b/mobile/src/main/res/layout/openhabgeofenceslist_item.xml index ccf4679340..d8d17d7ab1 100644 --- a/mobile/src/main/res/layout/openhabgeofenceslist_item.xml +++ b/mobile/src/main/res/layout/openhabgeofenceslist_item.xml @@ -55,7 +55,7 @@ diff --git a/mobile/src/main/res/values/strings.xml b/mobile/src/main/res/values/strings.xml index a5edf8e9a9..9fd28099ed 100644 --- a/mobile/src/main/res/values/strings.xml +++ b/mobile/src/main/res/values/strings.xml @@ -147,6 +147,15 @@ Current color Navigate to sitemap page + + New Geofence + Label + Name + Coord. + Radius + Create + Cancel + Sitemap drawer opened Sitemap drawer closed