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 com.android.server;
18 
19 import android.content.Context;
20 import android.location.Country;
21 import android.location.CountryListener;
22 import android.location.ICountryDetector;
23 import android.location.ICountryListener;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.text.TextUtils;
28 import android.util.PrintWriterPrinter;
29 import android.util.Printer;
30 import android.util.Slog;
31 
32 import com.android.internal.R;
33 import com.android.internal.annotations.VisibleForTesting;
34 import com.android.internal.os.BackgroundThread;
35 import com.android.internal.util.DumpUtils;
36 import com.android.server.location.countrydetector.ComprehensiveCountryDetector;
37 import com.android.server.location.countrydetector.CountryDetectorBase;
38 
39 import java.io.FileDescriptor;
40 import java.io.PrintWriter;
41 import java.lang.reflect.InvocationTargetException;
42 import java.util.HashMap;
43 
44 /**
45  * This class detects the country that the user is in. The default country detection is made through
46  * {@link ComprehensiveCountryDetector}. It is possible to overlay the detection algorithm by
47  * overlaying the attribute R.string.config_customCountryDetector with the custom class name to use
48  * instead. The custom class must extend {@link CountryDetectorBase}
49  *
50  * @hide
51  */
52 public class CountryDetectorService extends ICountryDetector.Stub {
53 
54     /**
55      * The class represents the remote listener, it will also removes itself from listener list when
56      * the remote process was died.
57      */
58     private final class Receiver implements IBinder.DeathRecipient {
59         private final ICountryListener mListener;
60         private final IBinder mKey;
61 
Receiver(ICountryListener listener)62         public Receiver(ICountryListener listener) {
63             mListener = listener;
64             mKey = listener.asBinder();
65         }
66 
binderDied()67         public void binderDied() {
68             removeListener(mKey);
69         }
70 
71         @Override
equals(Object otherObj)72         public boolean equals(Object otherObj) {
73             if (otherObj instanceof Receiver) {
74                 return mKey.equals(((Receiver) otherObj).mKey);
75             }
76             return false;
77         }
78 
79         @Override
hashCode()80         public int hashCode() {
81             return mKey.hashCode();
82         }
83 
getListener()84         public ICountryListener getListener() {
85             return mListener;
86         }
87     }
88 
89     private static final String TAG = "CountryDetector";
90 
91     /**
92      * Whether to dump the state of the country detector service to bugreports
93      */
94     private static final boolean DEBUG = false;
95 
96     private final HashMap<IBinder, Receiver> mReceivers;
97     private final Context mContext;
98     private CountryDetectorBase mCountryDetector;
99     private boolean mSystemReady;
100     private Handler mHandler;
101     private CountryListener mLocationBasedDetectorListener;
102 
CountryDetectorService(Context context)103     public CountryDetectorService(Context context) {
104         this(context, BackgroundThread.getHandler());
105     }
106 
107     @VisibleForTesting
CountryDetectorService(Context context, Handler handler)108     CountryDetectorService(Context context, Handler handler) {
109         super();
110         mReceivers = new HashMap<>();
111         mContext = context;
112         mHandler = handler;
113     }
114 
115     @Override
detectCountry()116     public Country detectCountry() {
117         if (!mSystemReady) {
118             return null; // server not yet active
119         } else {
120             return mCountryDetector.detectCountry();
121         }
122     }
123 
124     /**
125      * Add the ICountryListener into the listener list.
126      */
127     @Override
addCountryListener(ICountryListener listener)128     public void addCountryListener(ICountryListener listener) throws RemoteException {
129         if (!mSystemReady) {
130             throw new RemoteException();
131         }
132         addListener(listener);
133     }
134 
135     /**
136      * Remove the ICountryListener from the listener list.
137      */
138     @Override
removeCountryListener(ICountryListener listener)139     public void removeCountryListener(ICountryListener listener) throws RemoteException {
140         if (!mSystemReady) {
141             throw new RemoteException();
142         }
143         removeListener(listener.asBinder());
144     }
145 
addListener(ICountryListener listener)146     private void addListener(ICountryListener listener) {
147         synchronized (mReceivers) {
148             Receiver r = new Receiver(listener);
149             try {
150                 listener.asBinder().linkToDeath(r, 0);
151                 final Country country = detectCountry();
152                 if (country != null) {
153                     listener.onCountryDetected(country);
154                 }
155                 mReceivers.put(listener.asBinder(), r);
156                 if (mReceivers.size() == 1) {
157                     Slog.d(TAG, "The first listener is added");
158                     setCountryListener(mLocationBasedDetectorListener);
159                 }
160             } catch (RemoteException e) {
161                 Slog.e(TAG, "linkToDeath failed:", e);
162             }
163         }
164     }
165 
removeListener(IBinder key)166     private void removeListener(IBinder key) {
167         synchronized (mReceivers) {
168             mReceivers.remove(key);
169             if (mReceivers.isEmpty()) {
170                 setCountryListener(null);
171                 Slog.d(TAG, "No listener is left");
172             }
173         }
174     }
175 
notifyReceivers(Country country)176     protected void notifyReceivers(Country country) {
177         synchronized (mReceivers) {
178             for (Receiver receiver : mReceivers.values()) {
179                 try {
180                     receiver.getListener().onCountryDetected(country);
181                 } catch (RemoteException e) {
182                     // TODO: Shall we remove the receiver?
183                     Slog.e(TAG, "notifyReceivers failed:", e);
184                 }
185             }
186         }
187     }
188 
systemRunning()189     void systemRunning() {
190         // Shall we wait for the initialization finish.
191         mHandler.post(
192                 () -> {
193                     initialize();
194                     mSystemReady = true;
195                 });
196     }
197 
198     @VisibleForTesting
initialize()199     void initialize() {
200         final String customCountryClass = mContext.getString(R.string.config_customCountryDetector);
201         if (!TextUtils.isEmpty(customCountryClass)) {
202             mCountryDetector = loadCustomCountryDetectorIfAvailable(customCountryClass);
203         }
204 
205         if (mCountryDetector == null) {
206             Slog.d(TAG, "Using default country detector");
207             mCountryDetector = new ComprehensiveCountryDetector(mContext);
208         }
209         mLocationBasedDetectorListener = country -> mHandler.post(() -> notifyReceivers(country));
210     }
211 
setCountryListener(final CountryListener listener)212     protected void setCountryListener(final CountryListener listener) {
213         mHandler.post(() -> mCountryDetector.setCountryListener(listener));
214     }
215 
216     @VisibleForTesting
getCountryDetector()217     CountryDetectorBase getCountryDetector() {
218         return mCountryDetector;
219     }
220 
221     @VisibleForTesting
isSystemReady()222     boolean isSystemReady() {
223         return mSystemReady;
224     }
225 
loadCustomCountryDetectorIfAvailable( final String customCountryClass)226     private CountryDetectorBase loadCustomCountryDetectorIfAvailable(
227             final String customCountryClass) {
228         CountryDetectorBase customCountryDetector = null;
229 
230         Slog.d(TAG, "Using custom country detector class: " + customCountryClass);
231         try {
232             customCountryDetector = Class.forName(customCountryClass).asSubclass(
233                     CountryDetectorBase.class).getConstructor(Context.class).newInstance(
234                     mContext);
235         } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
236                 | NoSuchMethodException | InvocationTargetException e) {
237             Slog.e(TAG, "Could not instantiate the custom country detector class");
238         }
239 
240         return customCountryDetector;
241     }
242 
243     @SuppressWarnings("unused")
244     @Override
dump(FileDescriptor fd, PrintWriter fout, String[] args)245     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
246         if (!DumpUtils.checkDumpPermission(mContext, TAG, fout)) return;
247         if (!DEBUG) return;
248         try {
249             final Printer p = new PrintWriterPrinter(fout);
250             p.println("CountryDetectorService state:");
251             p.println("Country detector class=" + mCountryDetector.getClass().getName());
252             p.println("  Number of listeners=" + mReceivers.keySet().size());
253             if (mCountryDetector == null) {
254                 p.println("  CountryDetector not initialized");
255             } else {
256                 p.println("  " + mCountryDetector.toString());
257             }
258         } catch (Exception e) {
259             Slog.e(TAG, "Failed to dump CountryDetectorService: ", e);
260         }
261     }
262 }
263