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