1 /*
2  * Copyright (C) 2016 The Android Open Source Project
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.android.tv.common.util;
18 
19 import android.content.Context;
20 import android.location.Address;
21 import android.location.Geocoder;
22 import android.location.Location;
23 import android.location.LocationListener;
24 import android.location.LocationManager;
25 import android.os.Bundle;
26 import android.support.annotation.NonNull;
27 import android.support.annotation.Nullable;
28 import android.text.TextUtils;
29 import android.util.Log;
30 
31 import com.android.tv.common.BuildConfig;
32 
33 import java.io.IOException;
34 import java.util.Collections;
35 import java.util.HashSet;
36 import java.util.List;
37 import java.util.Locale;
38 import java.util.Set;
39 
40 /** A utility class to get the current location. */
41 public class LocationUtils {
42     private static final String TAG = "LocationUtils";
43     private static final boolean DEBUG = false;
44 
45     private static final Set<OnUpdateAddressListener> sOnUpdateAddressListeners =
46             Collections.synchronizedSet(new HashSet<>());
47 
48     private static Context sApplicationContext;
49     private static Address sAddress;
50     private static String sCountry;
51     private static IOException sError;
52 
53     /** Checks the current location. */
getCurrentAddress(Context context)54     public static synchronized Address getCurrentAddress(Context context)
55             throws IOException, SecurityException {
56         if (sAddress != null) {
57             return sAddress;
58         }
59         if (sError != null) {
60             throw sError;
61         }
62         if (sApplicationContext == null) {
63             sApplicationContext = context.getApplicationContext();
64         }
65         /* Begin_AOSP_Comment_Out
66         if (!BuildConfig.AOSP) {
67             com.google.android.tv.livechannels.util.GoogleLocationUtilsHelper.startLocationUpdates(
68                     context, LocationUtils::updateAddress);
69             return null;
70         }
71         End_AOSP_Comment_Out */
72         LocationUtilsHelper.startLocationUpdates();
73         return null;
74     }
75 
76     @Nullable
getCurrentPostalCode(Context context)77     static String getCurrentPostalCode(Context context) throws IOException {
78         Address address = getCurrentAddress(context);
79         if (address != null) {
80             Log.i(
81                     TAG,
82                     "Current country and postal code is "
83                             + address.getCountryName()
84                             + ", "
85                             + address.getPostalCode());
86             return address.getPostalCode();
87         }
88         return null;
89     }
90 
91     /** The listener used when address is updated. */
92     public interface OnUpdateAddressListener {
93         /**
94          * Called when address is updated.
95          *
96          * <p>This listener is removed when this method returns true.
97          *
98          * @return {@code true} if the job has been finished and the listener needs to be removed;
99          *     {@code false} otherwise.
100          */
onUpdateAddress(Address address)101         boolean onUpdateAddress(Address address);
102     }
103 
104     /**
105      * Add an {@link OnUpdateAddressListener} instance.
106      *
107      * <p>Note that the listener is removed automatically when {@link
108      * OnUpdateAddressListener#onUpdateAddress(Address)} is called and returns {@code true}.
109      */
addOnUpdateAddressListener(OnUpdateAddressListener listener)110     public static void addOnUpdateAddressListener(OnUpdateAddressListener listener) {
111         sOnUpdateAddressListeners.add(listener);
112     }
113 
114     /**
115      * Remove an {@link OnUpdateAddressListener} instance if it exists.
116      *
117      * <p>Note that the listener will be removed automatically when {@link
118      * OnUpdateAddressListener#onUpdateAddress(Address)} is called and returns {@code true}.
119      */
removeOnUpdateAddressListener(OnUpdateAddressListener listener)120     public static void removeOnUpdateAddressListener(OnUpdateAddressListener listener) {
121         sOnUpdateAddressListeners.remove(listener);
122     }
123 
124     /** Returns the current country. */
125     @NonNull
getCurrentCountry(Context context)126     public static synchronized String getCurrentCountry(Context context) {
127         if (sCountry != null) {
128             return sCountry;
129         }
130         /* Begin_AOSP_Comment_Out
131         if (!BuildConfig.AOSP) {
132             sCountry =
133                     com.google.android.tv.livechannels.util.GoogleLocationUtilsHelper
134                             .getDeviceCountry(context);
135         }
136         End_AOSP_Comment_Out */
137         if (TextUtils.isEmpty(sCountry)) {
138             sCountry = context.getResources().getConfiguration().locale.getCountry();
139         }
140         return sCountry;
141     }
142 
updateAddress(Location location)143     private static void updateAddress(Location location) {
144         if (DEBUG) Log.d(TAG, "Updating address with " + location);
145         if (location == null) {
146             return;
147         }
148         Geocoder geocoder = new Geocoder(sApplicationContext, Locale.getDefault());
149         try {
150             List<Address> addresses =
151                     geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1);
152             if (addresses != null && !addresses.isEmpty()) {
153                 sAddress = addresses.get(0);
154                 if (DEBUG) Log.d(TAG, "Got " + sAddress);
155                 try {
156                     PostalCodeUtils.updatePostalCode(sApplicationContext);
157                 } catch (Exception e) {
158                     // Do nothing
159                 }
160                 Set<OnUpdateAddressListener> listenersToRemove = new HashSet<>();
161                 synchronized (sOnUpdateAddressListeners) {
162                     for (OnUpdateAddressListener listener : sOnUpdateAddressListeners) {
163                         if (listener.onUpdateAddress(sAddress)) {
164                             listenersToRemove.add(listener);
165                         }
166                     }
167                     for (OnUpdateAddressListener listener : listenersToRemove) {
168                         removeOnUpdateAddressListener(listener);
169                     }
170                 }
171             } else {
172                 if (DEBUG) Log.d(TAG, "No address returned");
173             }
174             sError = null;
175         } catch (IOException e) {
176             Log.w(TAG, "Error in updating address", e);
177             sError = e;
178         }
179     }
180 
LocationUtils()181     private LocationUtils() {}
182 
183     private static class LocationUtilsHelper {
184         private static final LocationListener LOCATION_LISTENER =
185                 new LocationListener() {
186                     @Override
187                     public void onLocationChanged(Location location) {
188                         updateAddress(location);
189                     }
190 
191                     @Override
192                     public void onStatusChanged(String provider, int status, Bundle extras) {}
193 
194                     @Override
195                     public void onProviderEnabled(String provider) {}
196 
197                     @Override
198                     public void onProviderDisabled(String provider) {}
199                 };
200 
201         private static LocationManager sLocationManager;
202 
startLocationUpdates()203         public static void startLocationUpdates() {
204             if (sLocationManager == null) {
205                 sLocationManager =
206                         (LocationManager)
207                                 sApplicationContext.getSystemService(Context.LOCATION_SERVICE);
208                 try {
209                     sLocationManager.requestLocationUpdates(
210                             LocationManager.NETWORK_PROVIDER, 1000, 10, LOCATION_LISTENER, null);
211                 } catch (SecurityException e) {
212                     // Enables requesting the location updates again.
213                     sLocationManager = null;
214                     throw e;
215                 }
216             }
217         }
218     }
219 }
220