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