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