[go: nahoru, domu]

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.annotation.Nullable;
20import android.content.BroadcastReceiver;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.ServiceConnection;
26import android.content.pm.PackageInfo;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.pm.ResolveInfo;
30import android.content.pm.Signature;
31import android.content.res.Resources;
32import android.os.Handler;
33import android.os.IBinder;
34import android.os.UserHandle;
35import android.util.Log;
36import android.util.Slog;
37
38import com.android.internal.annotations.GuardedBy;
39import com.android.internal.content.PackageMonitor;
40
41import java.util.ArrayList;
42import java.util.Arrays;
43import java.util.HashSet;
44import java.util.List;
45import java.util.Objects;
46
47/**
48 * Find the best Service, and bind to it.
49 * Handles run-time package changes.
50 */
51public class ServiceWatcher implements ServiceConnection {
52    private static final boolean D = false;
53    public static final String EXTRA_SERVICE_VERSION = "serviceVersion";
54    public static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
55
56    private final String mTag;
57    private final Context mContext;
58    private final PackageManager mPm;
59    private final List<HashSet<Signature>> mSignatureSets;
60    private final String mAction;
61
62    /**
63     * If mServicePackageName is not null, only this package will be searched for the service that
64     * implements mAction. When null, all packages in the system that matches one of the signature
65     * in mSignatureSets are searched.
66     */
67    private final String mServicePackageName;
68    private final Runnable mNewServiceWork;
69    private final Handler mHandler;
70
71    private final Object mLock = new Object();
72
73    @GuardedBy("mLock")
74    private int mCurrentUserId = UserHandle.USER_SYSTEM;
75
76    @GuardedBy("mLock")
77    private IBinder mBoundService;
78    @GuardedBy("mLock")
79    private ComponentName mBoundComponent;
80    @GuardedBy("mLock")
81    private String mBoundPackageName;
82    @GuardedBy("mLock")
83    private int mBoundVersion = Integer.MIN_VALUE;
84    @GuardedBy("mLock")
85    private int mBoundUserId = UserHandle.USER_NULL;
86
87    public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
88            List<String> initialPackageNames) {
89        PackageManager pm = context.getPackageManager();
90        ArrayList<HashSet<Signature>> sigSets = new ArrayList<HashSet<Signature>>();
91        for (int i = 0, size = initialPackageNames.size(); i < size; i++) {
92            String pkg = initialPackageNames.get(i);
93            try {
94                HashSet<Signature> set = new HashSet<Signature>();
95                Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.MATCH_SYSTEM_ONLY
96                        | PackageManager.GET_SIGNATURES).signatures;
97                set.addAll(Arrays.asList(sigs));
98                sigSets.add(set);
99            } catch (NameNotFoundException e) {
100                Log.w("ServiceWatcher", pkg + " not found");
101            }
102        }
103        return sigSets;
104    }
105
106    public ServiceWatcher(Context context, String logTag, String action,
107            int overlaySwitchResId, int defaultServicePackageNameResId,
108            int initialPackageNamesResId, Runnable newServiceWork,
109            Handler handler) {
110        mContext = context;
111        mTag = logTag;
112        mAction = action;
113        mPm = mContext.getPackageManager();
114        mNewServiceWork = newServiceWork;
115        mHandler = handler;
116        Resources resources = context.getResources();
117
118        // Whether to enable service overlay.
119        boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
120        ArrayList<String> initialPackageNames = new ArrayList<String>();
121        if (enableOverlay) {
122            // A list of package names used to create the signatures.
123            String[] pkgs = resources.getStringArray(initialPackageNamesResId);
124            if (pkgs != null) initialPackageNames.addAll(Arrays.asList(pkgs));
125            mServicePackageName = null;
126            if (D) Log.d(mTag, "Overlay enabled, packages=" + Arrays.toString(pkgs));
127        } else {
128            // The default package name that is searched for service implementation when overlay is
129            // disabled.
130            String servicePackageName = resources.getString(defaultServicePackageNameResId);
131            if (servicePackageName != null) initialPackageNames.add(servicePackageName);
132            mServicePackageName = servicePackageName;
133            if (D) Log.d(mTag, "Overlay disabled, default package=" + servicePackageName);
134        }
135        mSignatureSets = getSignatureSets(context, initialPackageNames);
136    }
137
138    /**
139     * Start this watcher, including binding to the current best match and
140     * re-binding to any better matches down the road.
141     * <p>
142     * Note that if there are no matching encryption-aware services, we may not
143     * bind to a real service until after the current user is unlocked.
144     */
145    public boolean start() {
146        synchronized (mLock) {
147            bindBestPackageLocked(mServicePackageName, false);
148        }
149
150        // listen for user change
151        IntentFilter intentFilter = new IntentFilter();
152        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
153        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
154        mContext.registerReceiverAsUser(new BroadcastReceiver() {
155            @Override
156            public void onReceive(Context context, Intent intent) {
157                final String action = intent.getAction();
158                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
159                        UserHandle.USER_NULL);
160                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
161                    switchUser(userId);
162                } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
163                    unlockUser(userId);
164                }
165            }
166        }, UserHandle.ALL, intentFilter, null, mHandler);
167
168        // listen for relevant package changes if service overlay is enabled.
169        if (mServicePackageName == null) {
170            mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
171        }
172
173        return true;
174    }
175
176    /**
177     * Searches and binds to the best package, or do nothing if the best package
178     * is already bound, unless force rebinding is requested.
179     *
180     * @param justCheckThisPackage Only consider this package, or consider all
181     *            packages if it is {@code null}.
182     * @param forceRebind Force a rebinding to the best package if it's already
183     *            bound.
184     * @return {@code true} if a valid package was found to bind to.
185     */
186    private boolean bindBestPackageLocked(String justCheckThisPackage, boolean forceRebind) {
187        Intent intent = new Intent(mAction);
188        if (justCheckThisPackage != null) {
189            intent.setPackage(justCheckThisPackage);
190        }
191        final List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
192                PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
193                mCurrentUserId);
194        int bestVersion = Integer.MIN_VALUE;
195        ComponentName bestComponent = null;
196        boolean bestIsMultiuser = false;
197        if (rInfos != null) {
198            for (ResolveInfo rInfo : rInfos) {
199                final ComponentName component = rInfo.serviceInfo.getComponentName();
200                final String packageName = component.getPackageName();
201
202                // check signature
203                try {
204                    PackageInfo pInfo;
205                    pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES
206                            | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
207                    if (!isSignatureMatch(pInfo.signatures)) {
208                        Log.w(mTag, packageName + " resolves service " + mAction
209                                + ", but has wrong signature, ignoring");
210                        continue;
211                    }
212                } catch (NameNotFoundException e) {
213                    Log.wtf(mTag, e);
214                    continue;
215                }
216
217                // check metadata
218                int version = Integer.MIN_VALUE;
219                boolean isMultiuser = false;
220                if (rInfo.serviceInfo.metaData != null) {
221                    version = rInfo.serviceInfo.metaData.getInt(
222                            EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
223                    isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
224                }
225
226                if (version > bestVersion) {
227                    bestVersion = version;
228                    bestComponent = component;
229                    bestIsMultiuser = isMultiuser;
230                }
231            }
232
233            if (D) {
234                Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
235                        (justCheckThisPackage == null ? ""
236                                : "(" + justCheckThisPackage + ") "), rInfos.size(),
237                        (bestComponent == null ? "no new best component"
238                                : "new best component: " + bestComponent)));
239            }
240        } else {
241            if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
242        }
243
244        if (bestComponent == null) {
245            Slog.w(mTag, "Odd, no component found for service " + mAction);
246            unbindLocked();
247            return false;
248        }
249
250        final int userId = bestIsMultiuser ? UserHandle.USER_SYSTEM : mCurrentUserId;
251        final boolean alreadyBound = Objects.equals(bestComponent, mBoundComponent)
252                && bestVersion == mBoundVersion && userId == mBoundUserId;
253        if (forceRebind || !alreadyBound) {
254            unbindLocked();
255            bindToPackageLocked(bestComponent, bestVersion, userId);
256        }
257        return true;
258    }
259
260    private void unbindLocked() {
261        ComponentName component;
262        component = mBoundComponent;
263        mBoundComponent = null;
264        mBoundPackageName = null;
265        mBoundVersion = Integer.MIN_VALUE;
266        mBoundUserId = UserHandle.USER_NULL;
267        if (component != null) {
268            if (D) Log.d(mTag, "unbinding " + component);
269            mContext.unbindService(this);
270        }
271    }
272
273    private void bindToPackageLocked(ComponentName component, int version, int userId) {
274        Intent intent = new Intent(mAction);
275        intent.setComponent(component);
276        mBoundComponent = component;
277        mBoundPackageName = component.getPackageName();
278        mBoundVersion = version;
279        mBoundUserId = userId;
280        if (D) Log.d(mTag, "binding " + component + " (v" + version + ") (u" + userId + ")");
281        mContext.bindServiceAsUser(intent, this,
282                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE,
283                new UserHandle(userId));
284    }
285
286    public static boolean isSignatureMatch(Signature[] signatures,
287            List<HashSet<Signature>> sigSets) {
288        if (signatures == null) return false;
289
290        // build hashset of input to test against
291        HashSet<Signature> inputSet = new HashSet<Signature>();
292        for (Signature s : signatures) {
293            inputSet.add(s);
294        }
295
296        // test input against each of the signature sets
297        for (HashSet<Signature> referenceSet : sigSets) {
298            if (referenceSet.equals(inputSet)) {
299                return true;
300            }
301        }
302        return false;
303    }
304
305    private boolean isSignatureMatch(Signature[] signatures) {
306        return isSignatureMatch(signatures, mSignatureSets);
307    }
308
309    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
310        /**
311         * Called when package has been reinstalled
312         */
313        @Override
314        public void onPackageUpdateFinished(String packageName, int uid) {
315            synchronized (mLock) {
316                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
317                bindBestPackageLocked(null, forceRebind);
318            }
319        }
320
321        @Override
322        public void onPackageAdded(String packageName, int uid) {
323            synchronized (mLock) {
324                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
325                bindBestPackageLocked(null, forceRebind);
326            }
327        }
328
329        @Override
330        public void onPackageRemoved(String packageName, int uid) {
331            synchronized (mLock) {
332                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
333                bindBestPackageLocked(null, forceRebind);
334            }
335        }
336
337        @Override
338        public boolean onPackageChanged(String packageName, int uid, String[] components) {
339            synchronized (mLock) {
340                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
341                bindBestPackageLocked(null, forceRebind);
342            }
343            return super.onPackageChanged(packageName, uid, components);
344        }
345    };
346
347    @Override
348    public void onServiceConnected(ComponentName component, IBinder binder) {
349        synchronized (mLock) {
350            if (component.equals(mBoundComponent)) {
351                if (D) Log.d(mTag, component + " connected");
352                mBoundService = binder;
353                if (mHandler !=null && mNewServiceWork != null) {
354                    mHandler.post(mNewServiceWork);
355                }
356            } else {
357                Log.w(mTag, "unexpected onServiceConnected: " + component);
358            }
359        }
360    }
361
362    @Override
363    public void onServiceDisconnected(ComponentName component) {
364        synchronized (mLock) {
365            if (D) Log.d(mTag, component + " disconnected");
366
367            if (component.equals(mBoundComponent)) {
368                mBoundService = null;
369            }
370        }
371    }
372
373    public @Nullable String getBestPackageName() {
374        synchronized (mLock) {
375            return mBoundPackageName;
376        }
377    }
378
379    public int getBestVersion() {
380        synchronized (mLock) {
381            return mBoundVersion;
382        }
383    }
384
385    public @Nullable IBinder getBinder() {
386        synchronized (mLock) {
387            return mBoundService;
388        }
389    }
390
391    public void switchUser(int userId) {
392        synchronized (mLock) {
393            mCurrentUserId = userId;
394            bindBestPackageLocked(mServicePackageName, false);
395        }
396    }
397
398    public void unlockUser(int userId) {
399        synchronized (mLock) {
400            if (userId == mCurrentUserId) {
401                bindBestPackageLocked(mServicePackageName, false);
402            }
403        }
404    }
405}
406