| /* |
| * 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.content.Context; |
| import android.content.SharedPreferences; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.Point; |
| import android.graphics.Rect; |
| import android.preference.PreferenceManager; |
| 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"; |
| |
| /** |
| * 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) { |
| 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<String> getNodes(GoogleApiClient client) { |
| Collection<String> results= new HashSet<String>(); |
| 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); |
| } |
| } |