CarrierServiceBindHelper.java revision 2dc7607bae115d015083afad7dec3a3beee7c4c3
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
17package com.android.internal.telephony;
18
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.content.ServiceConnection;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Message;
31import android.os.UserHandle;
32import android.telephony.SubscriptionManager;
33import android.telephony.TelephonyManager;
34import android.util.Log;
35import android.service.carrier.CarrierService;
36
37import com.android.internal.telephony.IccCardConstants;
38
39import java.io.FileDescriptor;
40import java.io.PrintWriter;
41import java.util.List;
42
43/**
44 * Manages long-lived bindings to carrier services
45 * @hide
46 */
47public class CarrierServiceBindHelper {
48    private static final String LOG_TAG = CarrierServiceBindHelper.class.getSimpleName();
49
50    private Context mContext;
51    private AppBinding[] mBindings;
52    private final BroadcastReceiver mReceiver = new PackageChangedBroadcastReceiver();
53
54    private static final int EVENT_BIND = 0;
55    private static final int EVENT_UNBIND = 1;
56    private static final int EVENT_BIND_TIMEOUT = 2;
57    private static final int EVENT_PACKAGE_CHANGED = 3;
58
59    private static final int BIND_TIMEOUT_MILLIS = 10000;
60
61    private Handler mHandler = new Handler() {
62        @Override
63        public void handleMessage(Message msg) {
64            String carrierPackageName;
65            AppBinding binding;
66            log("mHandler: " + msg.what);
67
68            CarrierServiceConnection connection;
69            switch (msg.what) {
70                case EVENT_BIND:
71                    binding = (AppBinding) msg.obj;
72                    log("Binding to phoneId: " + binding.getPhoneId());
73                    binding.bind();
74                    break;
75                case EVENT_BIND_TIMEOUT:
76                    binding = (AppBinding) msg.obj;
77                    log("Bind timeout for phoneId: " + binding.getPhoneId());
78                    binding.unbind();
79                    break;
80                case EVENT_UNBIND:
81                    binding = (AppBinding) msg.obj;
82                    log("Unbinding for phoneId: " + binding.getPhoneId());
83                    binding.unbind();
84                    break;
85                case EVENT_PACKAGE_CHANGED:
86                    carrierPackageName = (String) msg.obj;
87                    for (AppBinding appBinding : mBindings) {
88                        if (carrierPackageName.equals(appBinding.getPackage())) {
89                          log(carrierPackageName + " changed and corresponds to a phone. Rebinding.");
90                          appBinding.bind();
91                        }
92                    }
93                    break;
94            }
95        }
96    };
97
98    public CarrierServiceBindHelper(Context context) {
99        mContext = context;
100
101        int numPhones = TelephonyManager.from(context).getPhoneCount();
102        mBindings = new AppBinding[numPhones];
103
104        for (int phoneId = 0; phoneId < numPhones; phoneId++) {
105            mBindings[phoneId] = new AppBinding(phoneId);
106        }
107
108        // Register for package updates. Update app or uninstall app update will have all 3 intents,
109        // in the order or removed, added, replaced, all with extra_replace set to true.
110        IntentFilter pkgFilter = new IntentFilter();
111        pkgFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
112        pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
113        pkgFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
114        pkgFilter.addDataScheme("package");
115        context.registerReceiverAsUser(mReceiver, UserHandle.ALL, pkgFilter, null, null);
116    }
117
118    public void updateForPhoneId(int phoneId, String simState) {
119        log("update binding for phoneId: " + phoneId + " simState: " + simState);
120        if (!SubscriptionManager.isValidPhoneId(phoneId)) {
121            return;
122        }
123        // requires Java 7 for switch on string.
124        switch (simState) {
125            case IccCardConstants.INTENT_VALUE_ICC_ABSENT:
126            case IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR:
127            case IccCardConstants.INTENT_VALUE_ICC_UNKNOWN:
128                mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNBIND, mBindings[phoneId]));
129                break;
130            case IccCardConstants.INTENT_VALUE_ICC_LOADED:
131            case IccCardConstants.INTENT_VALUE_ICC_LOCKED:
132                mHandler.sendMessage(mHandler.obtainMessage(EVENT_BIND, mBindings[phoneId]));
133                break;
134        }
135    }
136
137    private class AppBinding {
138        private int phoneId;
139        private CarrierServiceConnection connection;
140        private int bindCount;
141        private long lastBindStartMillis;
142        private int unbindCount;
143        private long lastUnbindMillis;
144        private String carrierPackage;
145
146        public AppBinding(int phoneId) {
147            this.phoneId = phoneId;
148        }
149
150        public int getPhoneId() {
151            return phoneId;
152        }
153
154        public String getPackage() {
155            return carrierPackage;
156        }
157
158        public void handleConnectionDown() {
159            connection = null;
160        }
161
162        public boolean bind() {
163            // Make sure there is no existing binding for this phone
164            unbind();
165
166            // Get the package name for the carrier app
167            List<String> carrierPackageNames =
168                TelephonyManager.from(mContext).getCarrierPackageNamesForIntentAndPhone(
169                    new Intent(CarrierService.CARRIER_SERVICE_INTERFACE), phoneId
170                );
171
172            if (carrierPackageNames == null || carrierPackageNames.size() <= 0) {
173                log("No carrier app for: " + phoneId);
174                return false;
175            }
176
177            log("Found carrier app: " + carrierPackageNames);
178            carrierPackage = carrierPackageNames.get(0);
179
180            // Log debug information
181            bindCount++;
182            lastBindStartMillis = System.currentTimeMillis();
183
184            // Look up the carrier service
185            Intent carrierService = new Intent(CarrierService.CARRIER_SERVICE_INTERFACE);
186            carrierService.setPackage(carrierPackage);
187
188            ResolveInfo carrierResolveInfo = mContext.getPackageManager().resolveService(
189                carrierService, PackageManager.GET_META_DATA);
190            Bundle metadata = carrierResolveInfo.serviceInfo.metaData;
191
192            // Only bind if the service wants it
193            if (metadata == null ||
194                !metadata.getBoolean("android.service.carrier.LONG_LIVED_BINDING", false)) {
195                log("Carrier app does not want a long lived binding");
196                return false;
197            }
198
199            log("Binding to " + carrierPackage + " for phone " + phoneId);
200            connection = new CarrierServiceConnection(this);
201            mHandler.sendMessageDelayed(
202                mHandler.obtainMessage(EVENT_BIND_TIMEOUT, this),
203                BIND_TIMEOUT_MILLIS);
204
205            String error;
206            try {
207                if (mContext.bindService(carrierService, connection, Context.BIND_AUTO_CREATE)) {
208                    return true;
209                }
210
211                error = "bindService returned false";
212            } catch (SecurityException ex) {
213                error = ex.getMessage();
214            }
215
216            log("Unable to bind to " + carrierPackage + " for phone " + phoneId +
217                ". Error: " + error);
218            return false;
219        }
220
221        public void unbind() {
222            mHandler.removeMessages(EVENT_BIND_TIMEOUT, this);
223            if (connection == null) {
224                return;
225            }
226
227            // Log debug information
228            unbindCount++;
229            lastUnbindMillis = System.currentTimeMillis();
230
231            // Actually unbind
232            log("Unbinding from carrier app");
233            mContext.unbindService(connection);
234            connection = null;
235        }
236
237        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
238            pw.println("Carrier app binding for phone " + phoneId);
239            pw.println("  connection: " + connection);
240            pw.println("  bindCount: " + bindCount);
241            pw.println("  lastBindStartMillis: " + lastBindStartMillis);
242            pw.println("  unbindCount: " + unbindCount);
243            pw.println("  lastUnbindMillis: " + lastUnbindMillis);
244            pw.println();
245        }
246    }
247
248    private class CarrierServiceConnection implements ServiceConnection {
249        private IBinder service;
250        private AppBinding binding;
251
252        public CarrierServiceConnection(AppBinding binding) {
253            this.binding = binding;
254        }
255
256        @Override
257        public void onServiceConnected(ComponentName name, IBinder service) {
258            log("Connected to carrier app: " + name.flattenToString());
259            mHandler.removeMessages(EVENT_BIND_TIMEOUT, binding);
260            this.service = service;
261        }
262
263        @Override
264        public void onServiceDisconnected(ComponentName name) {
265            log("Disconnected from carrier app: " + name.flattenToString());
266            this.service = null;
267            this.binding.handleConnectionDown();
268        }
269    }
270
271    private class PackageChangedBroadcastReceiver extends BroadcastReceiver {
272        @Override
273        public void onReceive(Context context, Intent intent) {
274            String action = intent.getAction();
275            log("Receive action: " + action);
276            switch (action) {
277                case Intent.ACTION_PACKAGE_ADDED:
278                case Intent.ACTION_PACKAGE_REMOVED:
279                case Intent.ACTION_PACKAGE_REPLACED:
280                    int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
281                    String packageName = mContext.getPackageManager().getNameForUid(uid);
282                    if (packageName != null) {
283                      // We don't have a phoneId for arg1.
284                      mHandler.sendMessage(
285                              mHandler.obtainMessage(EVENT_PACKAGE_CHANGED, packageName));
286                    }
287                    break;
288
289            }
290        }
291    }
292
293    private static void log(String message) {
294        Log.d(LOG_TAG, message);
295    }
296
297    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
298        pw.println("CarrierServiceBindHelper:");
299        for (AppBinding binding : mBindings) {
300            binding.dump(fd, pw, args);
301        }
302    }
303}
304