1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package android.location;
18 
19 import android.annotation.SystemService;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.content.Context;
22 import android.os.Build;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.os.RemoteException;
26 import android.util.Log;
27 
28 import java.util.HashMap;
29 
30 /**
31  * This class provides access to the system country detector service. This
32  * service allows applications to obtain the country that the user is in.
33  * <p>
34  * The country will be detected in order of reliability, like
35  * <ul>
36  * <li>Mobile network</li>
37  * <li>Location</li>
38  * <li>SIM's country</li>
39  * <li>Phone's locale</li>
40  * </ul>
41  * <p>
42  * Call the {@link #detectCountry()} to get the available country immediately.
43  * <p>
44  * To be notified of the future country change, use the
45  * {@link #addCountryListener}
46  * <p>
47  *
48  * @hide
49  */
50 @SystemService(Context.COUNTRY_DETECTOR)
51 public class CountryDetector {
52 
53     /**
54      * The class to wrap the ICountryListener.Stub and CountryListener objects
55      * together. The CountryListener will be notified through the specific
56      * looper once the country changed and detected.
57      */
58     private final static class ListenerTransport extends ICountryListener.Stub {
59 
60         private final CountryListener mListener;
61 
62         private final Handler mHandler;
63 
ListenerTransport(CountryListener listener, Looper looper)64         public ListenerTransport(CountryListener listener, Looper looper) {
65             mListener = listener;
66             if (looper != null) {
67                 mHandler = new Handler(looper);
68             } else {
69                 mHandler = new Handler();
70             }
71         }
72 
onCountryDetected(final Country country)73         public void onCountryDetected(final Country country) {
74             mHandler.post(new Runnable() {
75                 public void run() {
76                     mListener.onCountryDetected(country);
77                 }
78             });
79         }
80     }
81 
82     private final static String TAG = "CountryDetector";
83     private final ICountryDetector mService;
84     private final HashMap<CountryListener, ListenerTransport> mListeners;
85 
86     /**
87      * @hide - hide this constructor because it has a parameter of type
88      *       ICountryDetector, which is a system private class. The right way to
89      *       create an instance of this class is using the factory
90      *       Context.getSystemService.
91      */
92     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
CountryDetector(ICountryDetector service)93     public CountryDetector(ICountryDetector service) {
94         mService = service;
95         mListeners = new HashMap<CountryListener, ListenerTransport>();
96     }
97 
98     /**
99      * Start detecting the country that the user is in.
100      *
101      * @return the country if it is available immediately, otherwise null will
102      *         be returned.
103      */
104     @UnsupportedAppUsage
detectCountry()105     public Country detectCountry() {
106         try {
107             return mService.detectCountry();
108         } catch (RemoteException e) {
109             Log.e(TAG, "detectCountry: RemoteException", e);
110             return null;
111         }
112     }
113 
114     /**
115      * Add a listener to receive the notification when the country is detected
116      * or changed.
117      *
118      * @param listener will be called when the country is detected or changed.
119      * @param looper a Looper object whose message queue will be used to
120      *        implement the callback mechanism. If looper is null then the
121      *        callbacks will be called on the main thread.
122      */
123     @UnsupportedAppUsage
addCountryListener(CountryListener listener, Looper looper)124     public void addCountryListener(CountryListener listener, Looper looper) {
125         synchronized (mListeners) {
126             if (!mListeners.containsKey(listener)) {
127                 ListenerTransport transport = new ListenerTransport(listener, looper);
128                 try {
129                     mService.addCountryListener(transport);
130                     mListeners.put(listener, transport);
131                 } catch (RemoteException e) {
132                     Log.e(TAG, "addCountryListener: RemoteException", e);
133                 }
134             }
135         }
136     }
137 
138     /**
139      * Remove the listener
140      */
141     @UnsupportedAppUsage
removeCountryListener(CountryListener listener)142     public void removeCountryListener(CountryListener listener) {
143         synchronized (mListeners) {
144             ListenerTransport transport = mListeners.get(listener);
145             if (transport != null) {
146                 try {
147                     mListeners.remove(listener);
148                     mService.removeCountryListener(transport);
149                 } catch (RemoteException e) {
150                     Log.e(TAG, "removeCountryListener: RemoteException", e);
151                 }
152             }
153         }
154     }
155 }
156