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