/* * Copyright 2015 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.example.android.xyztouristattractions.common; import android.Manifest; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; import android.graphics.Rect; import android.preference.PreferenceManager; import android.support.v4.content.ContextCompat; import android.util.Log; import android.view.Display; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.maps.model.LatLng; import com.google.android.gms.wearable.Asset; import com.google.android.gms.wearable.Node; import com.google.android.gms.wearable.NodeApi; import com.google.android.gms.wearable.Wearable; import com.google.maps.android.SphericalUtil; import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.text.NumberFormat; import java.util.Collection; import java.util.HashSet; /** * This class contains shared static utility methods that both the mobile and * wearable apps can use. */ public class Utils { private static final String TAG = Utils.class.getSimpleName(); private static final String PREFERENCES_LAT = "lat"; private static final String PREFERENCES_LNG = "lng"; private static final String PREFERENCES_GEOFENCE_ENABLED = "geofence"; private static final String DISTANCE_KM_POSTFIX = "km"; private static final String DISTANCE_M_POSTFIX = "m"; /** * Check if the app has access to fine location permission. On pre-M * devices this will always return true. */ public static boolean checkFineLocationPermission(Context context) { return PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfPermission( context, Manifest.permission.ACCESS_FINE_LOCATION); } /** * Calculate distance between two LatLng points and format it nicely for * display. As this is a sample, it only statically supports metric units. * A production app should check locale and support the correct units. */ public static String formatDistanceBetween(LatLng point1, LatLng point2) { if (point1 == null || point2 == null) { return null; } NumberFormat numberFormat = NumberFormat.getNumberInstance(); double distance = Math.round(SphericalUtil.computeDistanceBetween(point1, point2)); // Adjust to KM if M goes over 1000 (see javadoc of method for note // on only supporting metric) if (distance >= 1000) { numberFormat.setMaximumFractionDigits(1); return numberFormat.format(distance / 1000) + DISTANCE_KM_POSTFIX; } return numberFormat.format(distance) + DISTANCE_M_POSTFIX; } /** * Store the location in the app preferences. */ public static void storeLocation(Context context, LatLng location) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = prefs.edit(); editor.putLong(PREFERENCES_LAT, Double.doubleToRawLongBits(location.latitude)); editor.putLong(PREFERENCES_LNG, Double.doubleToRawLongBits(location.longitude)); editor.apply(); } /** * Fetch the location from app preferences. */ public static LatLng getLocation(Context context) { if (!checkFineLocationPermission(context)) { return null; } SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); Long lat = prefs.getLong(PREFERENCES_LAT, Long.MAX_VALUE); Long lng = prefs.getLong(PREFERENCES_LNG, Long.MAX_VALUE); if (lat != Long.MAX_VALUE && lng != Long.MAX_VALUE) { Double latDbl = Double.longBitsToDouble(lat); Double lngDbl = Double.longBitsToDouble(lng); return new LatLng(latDbl, lngDbl); } return null; } /** * Store if geofencing triggers will show a notification in app preferences. */ public static void storeGeofenceEnabled(Context context, boolean enable) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = prefs.edit(); editor.putBoolean(PREFERENCES_GEOFENCE_ENABLED, enable); editor.apply(); } /** * Retrieve if geofencing triggers should show a notification from app preferences. */ public static boolean getGeofenceEnabled(Context context) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); return prefs.getBoolean(PREFERENCES_GEOFENCE_ENABLED, true); } /** * Convert an asset into a bitmap object synchronously. Only call this * method from a background thread (it should never be called from the * main/UI thread as it blocks). */ public static Bitmap loadBitmapFromAsset(GoogleApiClient googleApiClient, Asset asset) { if (asset == null) { throw new IllegalArgumentException("Asset must be non-null"); } // convert asset into a file descriptor and block until it's ready InputStream assetInputStream = Wearable.DataApi.getFdForAsset( googleApiClient, asset).await().getInputStream(); if (assetInputStream == null) { Log.w(TAG, "Requested an unknown Asset."); return null; } // decode the stream into a bitmap return BitmapFactory.decodeStream(assetInputStream); } /** * Create a wearable asset from a bitmap. */ public static Asset createAssetFromBitmap(Bitmap bitmap) { if (bitmap != null) { final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteStream); return Asset.createFromBytes(byteStream.toByteArray()); } return null; } /** * Get a list of all wearable nodes that are connected synchronously. * Only call this method from a background thread (it should never be * called from the main/UI thread as it blocks). */ public static Collection getNodes(GoogleApiClient client) { Collection results= new HashSet(); NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(client).await(); for (Node node : nodes.getNodes()) { results.add(node.getId()); } return results; } /** * Calculates the square insets on a round device. If the system insets are not set * (set to 0) then the inner square of the circle is applied instead. * * @param display device default display * @param systemInsets the system insets * @return adjusted square insets for use on a round device */ public static Rect calculateBottomInsetsOnRoundDevice(Display display, Rect systemInsets) { Point size = new Point(); display.getSize(size); int width = size.x + systemInsets.left + systemInsets.right; int height = size.y + systemInsets.top + systemInsets.bottom; // Minimum inset to use on a round screen, calculated as a fixed percent of screen height int minInset = (int) (height * Constants.WEAR_ROUND_MIN_INSET_PERCENT); // Use system inset if it is larger than min inset, otherwise use min inset int bottomInset = systemInsets.bottom > minInset ? systemInsets.bottom : minInset; // Calculate left and right insets based on bottom inset double radius = width / 2; double apothem = radius - bottomInset; double chord = Math.sqrt(Math.pow(radius, 2) - Math.pow(apothem, 2)) * 2; int leftRightInset = (int) ((width - chord) / 2); Log.d(TAG, "calculateBottomInsetsOnRoundDevice: " + bottomInset + ", " + leftRightInset); return new Rect(leftRightInset, 0, leftRightInset, bottomInset); } }