Home | History | Annotate | Download | only in com.example.android.xyztouristattractions.common
      1 /*
      2  * Copyright 2015 Google Inc. All rights reserved.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.xyztouristattractions.common;
     18 
     19 import android.Manifest;
     20 import android.content.Context;
     21 import android.content.SharedPreferences;
     22 import android.content.pm.PackageManager;
     23 import android.graphics.Bitmap;
     24 import android.graphics.BitmapFactory;
     25 import android.graphics.Point;
     26 import android.graphics.Rect;
     27 import android.preference.PreferenceManager;
     28 import android.support.v4.content.ContextCompat;
     29 import android.util.Log;
     30 import android.view.Display;
     31 
     32 import com.google.android.gms.common.api.GoogleApiClient;
     33 import com.google.android.gms.maps.model.LatLng;
     34 import com.google.android.gms.wearable.Asset;
     35 import com.google.android.gms.wearable.Node;
     36 import com.google.android.gms.wearable.NodeApi;
     37 import com.google.android.gms.wearable.Wearable;
     38 import com.google.maps.android.SphericalUtil;
     39 
     40 import java.io.ByteArrayOutputStream;
     41 import java.io.InputStream;
     42 import java.text.NumberFormat;
     43 import java.util.Collection;
     44 import java.util.HashSet;
     45 
     46 /**
     47  * This class contains shared static utility methods that both the mobile and
     48  * wearable apps can use.
     49  */
     50 public class Utils {
     51     private static final String TAG = Utils.class.getSimpleName();
     52 
     53     private static final String PREFERENCES_LAT = "lat";
     54     private static final String PREFERENCES_LNG = "lng";
     55     private static final String PREFERENCES_GEOFENCE_ENABLED = "geofence";
     56     private static final String DISTANCE_KM_POSTFIX = "km";
     57     private static final String DISTANCE_M_POSTFIX = "m";
     58 
     59     /**
     60      * Check if the app has access to fine location permission. On pre-M
     61      * devices this will always return true.
     62      */
     63     public static boolean checkFineLocationPermission(Context context) {
     64         return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission(
     65                 context, Manifest.permission.ACCESS_FINE_LOCATION);
     66     }
     67 
     68     /**
     69      * Calculate distance between two LatLng points and format it nicely for
     70      * display. As this is a sample, it only statically supports metric units.
     71      * A production app should check locale and support the correct units.
     72      */
     73     public static String formatDistanceBetween(LatLng point1, LatLng point2) {
     74         if (point1 == null || point2 == null) {
     75             return null;
     76         }
     77 
     78         NumberFormat numberFormat = NumberFormat.getNumberInstance();
     79         double distance = Math.round(SphericalUtil.computeDistanceBetween(point1, point2));
     80 
     81         // Adjust to KM if M goes over 1000 (see javadoc of method for note
     82         // on only supporting metric)
     83         if (distance >= 1000) {
     84             numberFormat.setMaximumFractionDigits(1);
     85             return numberFormat.format(distance / 1000) + DISTANCE_KM_POSTFIX;
     86         }
     87         return numberFormat.format(distance) + DISTANCE_M_POSTFIX;
     88     }
     89 
     90     /**
     91      * Store the location in the app preferences.
     92      */
     93     public static void storeLocation(Context context, LatLng location) {
     94         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
     95         SharedPreferences.Editor editor = prefs.edit();
     96         editor.putLong(PREFERENCES_LAT, Double.doubleToRawLongBits(location.latitude));
     97         editor.putLong(PREFERENCES_LNG, Double.doubleToRawLongBits(location.longitude));
     98         editor.apply();
     99     }
    100 
    101     /**
    102      * Fetch the location from app preferences.
    103      */
    104     public static LatLng getLocation(Context context) {
    105         if (!checkFineLocationPermission(context)) {
    106             return null;
    107         }
    108 
    109         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    110         Long lat = prefs.getLong(PREFERENCES_LAT, Long.MAX_VALUE);
    111         Long lng = prefs.getLong(PREFERENCES_LNG, Long.MAX_VALUE);
    112         if (lat != Long.MAX_VALUE && lng != Long.MAX_VALUE) {
    113             Double latDbl = Double.longBitsToDouble(lat);
    114             Double lngDbl = Double.longBitsToDouble(lng);
    115             return new LatLng(latDbl, lngDbl);
    116         }
    117         return null;
    118     }
    119 
    120     /**
    121      * Store if geofencing triggers will show a notification in app preferences.
    122      */
    123     public static void storeGeofenceEnabled(Context context, boolean enable) {
    124         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    125         SharedPreferences.Editor editor = prefs.edit();
    126         editor.putBoolean(PREFERENCES_GEOFENCE_ENABLED, enable);
    127         editor.apply();
    128     }
    129 
    130     /**
    131      * Retrieve if geofencing triggers should show a notification from app preferences.
    132      */
    133     public static boolean getGeofenceEnabled(Context context) {
    134         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
    135         return prefs.getBoolean(PREFERENCES_GEOFENCE_ENABLED, true);
    136     }
    137 
    138     /**
    139      * Convert an asset into a bitmap object synchronously. Only call this
    140      * method from a background thread (it should never be called from the
    141      * main/UI thread as it blocks).
    142      */
    143     public static Bitmap loadBitmapFromAsset(GoogleApiClient googleApiClient, Asset asset) {
    144         if (asset == null) {
    145             throw new IllegalArgumentException("Asset must be non-null");
    146         }
    147         // convert asset into a file descriptor and block until it's ready
    148         InputStream assetInputStream = Wearable.DataApi.getFdForAsset(
    149                 googleApiClient, asset).await().getInputStream();
    150 
    151         if (assetInputStream == null) {
    152             Log.w(TAG, "Requested an unknown Asset.");
    153             return null;
    154         }
    155         // decode the stream into a bitmap
    156         return BitmapFactory.decodeStream(assetInputStream);
    157     }
    158 
    159     /**
    160      * Create a wearable asset from a bitmap.
    161      */
    162     public static Asset createAssetFromBitmap(Bitmap bitmap) {
    163         if (bitmap != null) {
    164             final ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
    165             bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream);
    166             return Asset.createFromBytes(byteStream.toByteArray());
    167         }
    168         return null;
    169     }
    170 
    171     /**
    172      * Get a list of all wearable nodes that are connected synchronously.
    173      * Only call this method from a background thread (it should never be
    174      * called from the main/UI thread as it blocks).
    175      */
    176     public static Collection<String> getNodes(GoogleApiClient client) {
    177         Collection<String> results= new HashSet<String>();
    178         NodeApi.GetConnectedNodesResult nodes =
    179                 Wearable.NodeApi.getConnectedNodes(client).await();
    180         for (Node node : nodes.getNodes()) {
    181             results.add(node.getId());
    182         }
    183         return results;
    184     }
    185 
    186     /**
    187      * Calculates the square insets on a round device. If the system insets are not set
    188      * (set to 0) then the inner square of the circle is applied instead.
    189      *
    190      * @param display device default display
    191      * @param systemInsets the system insets
    192      * @return adjusted square insets for use on a round device
    193      */
    194     public static Rect calculateBottomInsetsOnRoundDevice(Display display, Rect systemInsets) {
    195         Point size = new Point();
    196         display.getSize(size);
    197         int width = size.x + systemInsets.left + systemInsets.right;
    198         int height = size.y + systemInsets.top + systemInsets.bottom;
    199 
    200         // Minimum inset to use on a round screen, calculated as a fixed percent of screen height
    201         int minInset = (int) (height * Constants.WEAR_ROUND_MIN_INSET_PERCENT);
    202 
    203         // Use system inset if it is larger than min inset, otherwise use min inset
    204         int bottomInset = systemInsets.bottom > minInset ? systemInsets.bottom : minInset;
    205 
    206         // Calculate left and right insets based on bottom inset
    207         double radius = width / 2;
    208         double apothem = radius - bottomInset;
    209         double chord = Math.sqrt(Math.pow(radius, 2) - Math.pow(apothem, 2)) * 2;
    210         int leftRightInset = (int) ((width - chord) / 2);
    211 
    212         Log.d(TAG, "calculateBottomInsetsOnRoundDevice: " + bottomInset + ", " + leftRightInset);
    213 
    214         return new Rect(leftRightInset, 0, leftRightInset, bottomInset);
    215     }
    216 }
    217