• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2 * Copyright (C) 2015 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.internal.telephony;
18 
19 import android.annotation.UnsupportedAppUsage;
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.ServiceConnection;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ResolveInfo;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Message;
32 import android.os.Process;
33 import android.os.SystemClock;
34 import android.os.UserHandle;
35 import android.service.carrier.CarrierService;
36 import android.telephony.SubscriptionManager;
37 import android.telephony.TelephonyManager;
38 import android.text.TextUtils;
39 import android.util.Log;
40 
41 import com.android.internal.content.PackageMonitor;
42 
43 import java.io.FileDescriptor;
44 import java.io.PrintWriter;
45 import java.util.List;
46 
47 /**
48  * Manages long-lived bindings to carrier services
49  * @hide
50  */
51 public class CarrierServiceBindHelper {
52     private static final String LOG_TAG = "CarrierSvcBindHelper";
53 
54     /**
55      * How long to linger a binding after an app loses carrier privileges, as long as no new
56      * binding comes in to take its place.
57      */
58     private static final int UNBIND_DELAY_MILLIS = 30 * 1000; // 30 seconds
59 
60     @UnsupportedAppUsage
61     private Context mContext;
62     private AppBinding[] mBindings;
63     private String[] mLastSimState;
64     private final PackageMonitor mPackageMonitor = new CarrierServicePackageMonitor();
65 
66     private BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
67         @Override
68         public void onReceive(Context context, Intent intent) {
69             final String action = intent.getAction();
70             log("Received " + action);
71 
72             if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
73                 // On user unlock, new components might become available, so reevaluate all
74                 // bindings.
75                 for (int phoneId = 0; phoneId < mBindings.length; phoneId++) {
76                     mBindings[phoneId].rebind();
77                 }
78             }
79         }
80     };
81 
82     private static final int EVENT_REBIND = 0;
83     private static final int EVENT_PERFORM_IMMEDIATE_UNBIND = 1;
84 
85     @UnsupportedAppUsage
86     private Handler mHandler = new Handler() {
87         @Override
88         public void handleMessage(Message msg) {
89             AppBinding binding;
90             log("mHandler: " + msg.what);
91 
92             switch (msg.what) {
93                 case EVENT_REBIND:
94                     binding = (AppBinding) msg.obj;
95                     log("Rebinding if necessary for phoneId: " + binding.getPhoneId());
96                     binding.rebind();
97                     break;
98                 case EVENT_PERFORM_IMMEDIATE_UNBIND:
99                     binding = (AppBinding) msg.obj;
100                     binding.performImmediateUnbind();
101                     break;
102             }
103         }
104     };
105 
CarrierServiceBindHelper(Context context)106     public CarrierServiceBindHelper(Context context) {
107         mContext = context;
108 
109         int numPhones = TelephonyManager.from(context).getPhoneCount();
110         mBindings = new AppBinding[numPhones];
111         mLastSimState = new String[numPhones];
112 
113         for (int phoneId = 0; phoneId < numPhones; phoneId++) {
114             mBindings[phoneId] = new AppBinding(phoneId);
115         }
116 
117         mPackageMonitor.register(
118                 context, mHandler.getLooper(), UserHandle.ALL, false /* externalStorage */);
119         mContext.registerReceiverAsUser(mUserUnlockedReceiver, UserHandle.SYSTEM,
120                 new IntentFilter(Intent.ACTION_USER_UNLOCKED), null /* broadcastPermission */,
121                 mHandler);
122     }
123 
updateForPhoneId(int phoneId, String simState)124     void updateForPhoneId(int phoneId, String simState) {
125         log("update binding for phoneId: " + phoneId + " simState: " + simState);
126         if (!SubscriptionManager.isValidPhoneId(phoneId)) {
127             return;
128         }
129         if (TextUtils.isEmpty(simState) || phoneId >= mLastSimState.length) return;
130         if (simState.equals(mLastSimState[phoneId])) {
131             // ignore consecutive duplicated events
132             return;
133         } else {
134             mLastSimState[phoneId] = simState;
135         }
136         mHandler.sendMessage(mHandler.obtainMessage(EVENT_REBIND, mBindings[phoneId]));
137     }
138 
139     private class AppBinding {
140         private int phoneId;
141         private CarrierServiceConnection connection;
142         private int bindCount;
143         private long lastBindStartMillis;
144         private int unbindCount;
145         private long lastUnbindMillis;
146         private String carrierPackage;
147         private String carrierServiceClass;
148         private long mUnbindScheduledUptimeMillis = -1;
149 
AppBinding(int phoneId)150         public AppBinding(int phoneId) {
151             this.phoneId = phoneId;
152         }
153 
getPhoneId()154         public int getPhoneId() {
155             return phoneId;
156         }
157 
158         /** Return the package that is currently being bound to, or null if there is no binding. */
getPackage()159         public String getPackage() {
160             return carrierPackage;
161         }
162 
163         /**
164          * Update the bindings for the current carrier app for this phone.
165          *
166          * <p>Safe to call even if a binding already exists. If the current binding is invalid, it
167          * will be dropped. If it is valid, it will be left untouched.
168          */
rebind()169         void rebind() {
170             // Get the package name for the carrier app
171             List<String> carrierPackageNames =
172                 TelephonyManager.from(mContext).getCarrierPackageNamesForIntentAndPhone(
173                     new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId
174                 );
175 
176             if (carrierPackageNames == null || carrierPackageNames.size() <= 0) {
177                 log("No carrier app for: " + phoneId);
178                 // Unbind after a delay in case this is a temporary blip in carrier privileges.
179                 unbind(false /* immediate */);
180                 return;
181             }
182 
183             log("Found carrier app: " + carrierPackageNames);
184             String candidateCarrierPackage = carrierPackageNames.get(0);
185             // If we are binding to a different package, unbind immediately from the current one.
186             if (!TextUtils.equals(carrierPackage, candidateCarrierPackage)) {
187                 unbind(true /* immediate */);
188             }
189 
190             // Look up the carrier service
191             Intent carrierService = new Intent(CarrierService.CARRIER_SERVICE_INTERFACE);
192             carrierService.setPackage(candidateCarrierPackage);
193 
194             ResolveInfo carrierResolveInfo = mContext.getPackageManager().resolveService(
195                 carrierService, PackageManager.GET_META_DATA);
196             Bundle metadata = null;
197             String candidateServiceClass = null;
198             if (carrierResolveInfo != null) {
199                 metadata = carrierResolveInfo.serviceInfo.metaData;
200                 candidateServiceClass =
201                         carrierResolveInfo.getComponentInfo().getComponentName().getClassName();
202             }
203 
204             // Only bind if the service wants it
205             if (metadata == null ||
206                 !metadata.getBoolean("android.service.carrier.LONG_LIVED_BINDING", false)) {
207                 log("Carrier app does not want a long lived binding");
208                 unbind(true /* immediate */);
209                 return;
210             }
211 
212             if (!TextUtils.equals(carrierServiceClass, candidateServiceClass)) {
213                 // Unbind immediately if the carrier service component has changed.
214                 unbind(true /* immediate */);
215             } else if (connection != null) {
216                 // Component is unchanged and connection is up - do nothing, but cancel any
217                 // scheduled unbinds.
218                 cancelScheduledUnbind();
219                 return;
220             }
221 
222             carrierPackage = candidateCarrierPackage;
223             carrierServiceClass = candidateServiceClass;
224 
225             log("Binding to " + carrierPackage + " for phone " + phoneId);
226 
227             // Log debug information
228             bindCount++;
229             lastBindStartMillis = System.currentTimeMillis();
230 
231             connection = new CarrierServiceConnection();
232 
233             String error;
234             try {
235                 if (mContext.bindServiceAsUser(carrierService, connection,
236                         Context.BIND_AUTO_CREATE |  Context.BIND_FOREGROUND_SERVICE,
237                         mHandler, Process.myUserHandle())) {
238                     return;
239                 }
240 
241                 error = "bindService returned false";
242             } catch (SecurityException ex) {
243                 error = ex.getMessage();
244             }
245 
246             log("Unable to bind to " + carrierPackage + " for phone " + phoneId +
247                 ". Error: " + error);
248             unbind(true /* immediate */);
249         }
250 
251         /**
252          * Release the binding.
253          *
254          * @param immediate whether the binding should be released immediately or after a short
255          *                  delay. This should be true unless the reason for the unbind is that no
256          *                  app has carrier privileges, in which case it is useful to delay
257          *                  unbinding in case this is a temporary SIM blip.
258          */
unbind(boolean immediate)259         void unbind(boolean immediate) {
260             if (connection == null) {
261                 // Already fully unbound.
262                 return;
263             }
264 
265             // Only let the binding linger if a delayed unbind is requested *and* the connection is
266             // currently active. If the connection is down, unbind immediately as the app is likely
267             // not running anyway and it may be a permanent disconnection (e.g. the app was
268             // disabled).
269             if (immediate || !connection.connected) {
270                 cancelScheduledUnbind();
271                 performImmediateUnbind();
272             } else if (mUnbindScheduledUptimeMillis == -1) {
273                 long currentUptimeMillis = SystemClock.uptimeMillis();
274                 mUnbindScheduledUptimeMillis = currentUptimeMillis + UNBIND_DELAY_MILLIS;
275                 log("Scheduling unbind in " + UNBIND_DELAY_MILLIS + " millis");
276                 mHandler.sendMessageAtTime(
277                         mHandler.obtainMessage(EVENT_PERFORM_IMMEDIATE_UNBIND, this),
278                         mUnbindScheduledUptimeMillis);
279             }
280         }
281 
performImmediateUnbind()282         private void performImmediateUnbind() {
283             // Log debug information
284             unbindCount++;
285             lastUnbindMillis = System.currentTimeMillis();
286 
287             // Clear package state now that no binding is desired.
288             carrierPackage = null;
289             carrierServiceClass = null;
290 
291             // Actually unbind
292             log("Unbinding from carrier app");
293             mContext.unbindService(connection);
294             connection = null;
295             mUnbindScheduledUptimeMillis = -1;
296         }
297 
cancelScheduledUnbind()298         private void cancelScheduledUnbind() {
299             mHandler.removeMessages(EVENT_PERFORM_IMMEDIATE_UNBIND);
300             mUnbindScheduledUptimeMillis = -1;
301         }
302 
dump(FileDescriptor fd, PrintWriter pw, String[] args)303         public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
304             pw.println("Carrier app binding for phone " + phoneId);
305             pw.println("  connection: " + connection);
306             pw.println("  bindCount: " + bindCount);
307             pw.println("  lastBindStartMillis: " + lastBindStartMillis);
308             pw.println("  unbindCount: " + unbindCount);
309             pw.println("  lastUnbindMillis: " + lastUnbindMillis);
310             pw.println("  mUnbindScheduledUptimeMillis: " + mUnbindScheduledUptimeMillis);
311             pw.println();
312         }
313     }
314 
315     private class CarrierServiceConnection implements ServiceConnection {
316         private boolean connected;
317 
318         @Override
onServiceConnected(ComponentName name, IBinder service)319         public void onServiceConnected(ComponentName name, IBinder service) {
320             log("Connected to carrier app: " + name.flattenToString());
321             connected = true;
322         }
323 
324         @Override
onServiceDisconnected(ComponentName name)325         public void onServiceDisconnected(ComponentName name) {
326             log("Disconnected from carrier app: " + name.flattenToString());
327             connected = false;
328         }
329 
330         @Override
toString()331         public String toString() {
332             return "CarrierServiceConnection[connected=" + connected + "]";
333         }
334     }
335 
336     private class CarrierServicePackageMonitor extends PackageMonitor {
337         @Override
onPackageAdded(String packageName, int reason)338         public void onPackageAdded(String packageName, int reason) {
339             evaluateBinding(packageName, true /* forceUnbind */);
340         }
341 
342         @Override
onPackageRemoved(String packageName, int reason)343         public void onPackageRemoved(String packageName, int reason) {
344             evaluateBinding(packageName, true /* forceUnbind */);
345         }
346 
347         @Override
onPackageUpdateFinished(String packageName, int uid)348         public void onPackageUpdateFinished(String packageName, int uid) {
349             evaluateBinding(packageName, true /* forceUnbind */);
350         }
351 
352         @Override
onPackageModified(String packageName)353         public void onPackageModified(String packageName) {
354             evaluateBinding(packageName, false /* forceUnbind */);
355         }
356 
357         @Override
onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit)358         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
359             if (doit) {
360                 for (String packageName : packages) {
361                     evaluateBinding(packageName, true /* forceUnbind */);
362                 }
363             }
364             return super.onHandleForceStop(intent, packages, uid, doit);
365         }
366 
evaluateBinding(String carrierPackageName, boolean forceUnbind)367         private void evaluateBinding(String carrierPackageName, boolean forceUnbind) {
368             for (AppBinding appBinding : mBindings) {
369                 String appBindingPackage = appBinding.getPackage();
370                 boolean isBindingForPackage = carrierPackageName.equals(appBindingPackage);
371                 // Only log if this package was a carrier package to avoid log spam in the common
372                 // case that there are no carrier packages, but evaluate the binding if the package
373                 // is unset, in case this package change resulted in a new carrier package becoming
374                 // available for binding.
375                 if (isBindingForPackage) {
376                     log(carrierPackageName + " changed and corresponds to a phone. Rebinding.");
377                 }
378                 if (appBindingPackage == null || isBindingForPackage) {
379                     if (forceUnbind) {
380                         appBinding.unbind(true /* immediate */);
381                     }
382                     appBinding.rebind();
383                 }
384             }
385         }
386     }
387 
log(String message)388     private static void log(String message) {
389         Log.d(LOG_TAG, message);
390     }
391 
dump(FileDescriptor fd, PrintWriter pw, String[] args)392     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
393         pw.println("CarrierServiceBindHelper:");
394         for (AppBinding binding : mBindings) {
395             binding.dump(fd, pw, args);
396         }
397     }
398 }
399