1/*
2 * Copyright (C) 2012 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.server;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.content.pm.PackageInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageManager.NameNotFoundException;
26import android.content.pm.ResolveInfo;
27import android.content.pm.Signature;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.UserHandle;
31import android.util.Log;
32
33import com.android.internal.content.PackageMonitor;
34
35import java.util.ArrayList;
36import java.util.Arrays;
37import java.util.HashSet;
38import java.util.List;
39
40/**
41 * Find the best Service, and bind to it.
42 * Handles run-time package changes.
43 */
44public class ServiceWatcher implements ServiceConnection {
45    private static final boolean D = false;
46    public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
47
48    private final String mTag;
49    private final Context mContext;
50    private final PackageManager mPm;
51    private final List<HashSet<Signature>> mSignatureSets;
52    private final String mAction;
53    private final Runnable mNewServiceWork;
54    private final Handler mHandler;
55
56    private Object mLock = new Object();
57
58    // all fields below synchronized on mLock
59    private IBinder mBinder;   // connected service
60    private String mPackageName;  // current best package
61    private int mVersion = Integer.MIN_VALUE;  // current best version
62    private int mCurrentUserId;
63
64    public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
65            List<String> initialPackageNames) {
66        PackageManager pm = context.getPackageManager();
67        ArrayList<HashSet<Signature>> sigSets = new ArrayList<HashSet<Signature>>();
68        for (int i = 0, size = initialPackageNames.size(); i < size; i++) {
69            String pkg = initialPackageNames.get(i);
70            try {
71                HashSet<Signature> set = new HashSet<Signature>();
72                Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures;
73                set.addAll(Arrays.asList(sigs));
74                sigSets.add(set);
75            } catch (NameNotFoundException e) {
76                Log.w("ServiceWatcher", pkg + " not found");
77            }
78        }
79        return sigSets;
80    }
81
82    public ServiceWatcher(Context context, String logTag, String action,
83            List<String> initialPackageNames, Runnable newServiceWork, Handler handler, int userId) {
84        mContext = context;
85        mTag = logTag;
86        mAction = action;
87        mPm = mContext.getPackageManager();
88        mNewServiceWork = newServiceWork;
89        mHandler = handler;
90        mCurrentUserId = userId;
91
92        mSignatureSets = getSignatureSets(context, initialPackageNames);
93    }
94
95    public boolean start() {
96        synchronized (mLock) {
97            if (!bindBestPackageLocked(null)) return false;
98        }
99
100        mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
101        return true;
102    }
103
104    /**
105     * Searches and binds to the best package, or do nothing
106     * if the best package is already bound.
107     * Only checks the named package, or checks all packages if it
108     * is null.
109     * Return true if a new package was found to bind to.
110     */
111    private boolean bindBestPackageLocked(String justCheckThisPackage) {
112        Intent intent = new Intent(mAction);
113        if (justCheckThisPackage != null) {
114            intent.setPackage(justCheckThisPackage);
115        }
116        List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(new Intent(mAction),
117                PackageManager.GET_META_DATA, mCurrentUserId);
118        int bestVersion = Integer.MIN_VALUE;
119        String bestPackage = null;
120        for (ResolveInfo rInfo : rInfos) {
121            String packageName = rInfo.serviceInfo.packageName;
122
123            // check signature
124            try {
125                PackageInfo pInfo;
126                pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
127                if (!isSignatureMatch(pInfo.signatures)) {
128                    Log.w(mTag, packageName + " resolves service " + mAction +
129                            ", but has wrong signature, ignoring");
130                    continue;
131                }
132            } catch (NameNotFoundException e) {
133                Log.wtf(mTag, e);
134                continue;
135            }
136
137            // check version
138            int version = 0;
139            if (rInfo.serviceInfo.metaData != null) {
140                version = rInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, 0);
141            }
142
143            if (version > mVersion) {
144                bestVersion = version;
145                bestPackage = packageName;
146            }
147        }
148
149        if (D) Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
150                (justCheckThisPackage == null ? "" : "(" + justCheckThisPackage + ") "),
151                rInfos.size(),
152                (bestPackage == null ? "no new best package" : "new best packge: " + bestPackage)));
153
154        if (bestPackage != null) {
155            bindToPackageLocked(bestPackage, bestVersion);
156            return true;
157        }
158        return false;
159    }
160
161    private void unbindLocked() {
162        String pkg;
163        pkg = mPackageName;
164        mPackageName = null;
165        mVersion = Integer.MIN_VALUE;
166        if (pkg != null) {
167            if (D) Log.d(mTag, "unbinding " + pkg);
168            mContext.unbindService(this);
169        }
170    }
171
172    private void bindToPackageLocked(String packageName, int version) {
173        unbindLocked();
174        Intent intent = new Intent(mAction);
175        intent.setPackage(packageName);
176        mPackageName = packageName;
177        mVersion = version;
178        if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ")");
179        mContext.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
180                | Context.BIND_NOT_VISIBLE, mCurrentUserId);
181    }
182
183    public static boolean isSignatureMatch(Signature[] signatures,
184            List<HashSet<Signature>> sigSets) {
185        if (signatures == null) return false;
186
187        // build hashset of input to test against
188        HashSet<Signature> inputSet = new HashSet<Signature>();
189        for (Signature s : signatures) {
190            inputSet.add(s);
191        }
192
193        // test input against each of the signature sets
194        for (HashSet<Signature> referenceSet : sigSets) {
195            if (referenceSet.equals(inputSet)) {
196                return true;
197            }
198        }
199        return false;
200    }
201
202    private boolean isSignatureMatch(Signature[] signatures) {
203        return isSignatureMatch(signatures, mSignatureSets);
204    }
205
206    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
207        /**
208         * Called when package has been reinstalled
209         */
210        @Override
211        public void onPackageUpdateFinished(String packageName, int uid) {
212            synchronized (mLock) {
213                if (packageName.equals(mPackageName)) {
214                    // package updated, make sure to rebind
215                    unbindLocked();
216                }
217                // check the updated package in case it is better
218                bindBestPackageLocked(packageName);
219            }
220        }
221
222        @Override
223        public void onPackageAdded(String packageName, int uid) {
224            synchronized (mLock) {
225                if (packageName.equals(mPackageName)) {
226                    // package updated, make sure to rebind
227                    unbindLocked();
228                }
229                // check the new package is case it is better
230                bindBestPackageLocked(packageName);
231            }
232        }
233
234        @Override
235        public void onPackageRemoved(String packageName, int uid) {
236            synchronized (mLock) {
237                if (packageName.equals(mPackageName)) {
238                    unbindLocked();
239                    // the currently bound package was removed,
240                    // need to search for a new package
241                    bindBestPackageLocked(null);
242                }
243            }
244        }
245    };
246
247    @Override
248    public void onServiceConnected(ComponentName name, IBinder binder) {
249        synchronized (mLock) {
250            String packageName = name.getPackageName();
251            if (packageName.equals(mPackageName)) {
252                if (D) Log.d(mTag, packageName + " connected");
253                mBinder = binder;
254                if (mHandler !=null && mNewServiceWork != null) {
255                    mHandler.post(mNewServiceWork);
256                }
257            } else {
258                Log.w(mTag, "unexpected onServiceConnected: " + packageName);
259            }
260        }
261    }
262
263    @Override
264    public void onServiceDisconnected(ComponentName name) {
265        synchronized (mLock) {
266            String packageName = name.getPackageName();
267            if (D) Log.d(mTag, packageName + " disconnected");
268
269            if (packageName.equals(mPackageName)) {
270                mBinder = null;
271            }
272        }
273    }
274
275    public String getBestPackageName() {
276        synchronized (mLock) {
277            return mPackageName;
278        }
279    }
280
281    public int getBestVersion() {
282        synchronized (mLock) {
283            return mVersion;
284        }
285    }
286
287    public IBinder getBinder() {
288        synchronized (mLock) {
289            return mBinder;
290        }
291    }
292
293    public void switchUser(int userId) {
294        synchronized (mLock) {
295            unbindLocked();
296            mCurrentUserId = userId;
297            bindBestPackageLocked(null);
298        }
299    }
300}
301