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     * @returns {@code true} if a potential service implementation was found.
146     */
147    public boolean start() {
148        if (isServiceMissing()) return false;
149
150        synchronized (mLock) {
151            bindBestPackageLocked(mServicePackageName, false);
152        }
153
154        // listen for user change
155        IntentFilter intentFilter = new IntentFilter();
156        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
157        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
158        mContext.registerReceiverAsUser(new BroadcastReceiver() {
159            @Override
160            public void onReceive(Context context, Intent intent) {
161                final String action = intent.getAction();
162                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
163                        UserHandle.USER_NULL);
164                if (Intent.ACTION_USER_SWITCHED.equals(action)) {
165                    switchUser(userId);
166                } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
167                    unlockUser(userId);
168                }
169            }
170        }, UserHandle.ALL, intentFilter, null, mHandler);
171
172        // listen for relevant package changes if service overlay is enabled.
173        if (mServicePackageName == null) {
174            mPackageMonitor.register(mContext, null, UserHandle.ALL, true);
175        }
176
177        return true;
178    }
179
180    /**
181     * Check if any instance of this service is present on the device,
182     * regardless of it being encryption-aware or not.
183     */
184    private boolean isServiceMissing() {
185        final Intent intent = new Intent(mAction);
186        final int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
187                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
188        return mPm.queryIntentServicesAsUser(intent, flags, mCurrentUserId).isEmpty();
189    }
190
191    /**
192     * Searches and binds to the best package, or do nothing if the best package
193     * is already bound, unless force rebinding is requested.
194     *
195     * @param justCheckThisPackage Only consider this package, or consider all
196     *            packages if it is {@code null}.
197     * @param forceRebind Force a rebinding to the best package if it's already
198     *            bound.
199     * @returns {@code true} if a valid package was found to bind to.
200     */
201    private boolean bindBestPackageLocked(String justCheckThisPackage, boolean forceRebind) {
202        Intent intent = new Intent(mAction);
203        if (justCheckThisPackage != null) {
204            intent.setPackage(justCheckThisPackage);
205        }
206        final List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
207                PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
208                mCurrentUserId);
209        int bestVersion = Integer.MIN_VALUE;
210        ComponentName bestComponent = null;
211        boolean bestIsMultiuser = false;
212        if (rInfos != null) {
213            for (ResolveInfo rInfo : rInfos) {
214                final ComponentName component = rInfo.serviceInfo.getComponentName();
215                final String packageName = component.getPackageName();
216
217                // check signature
218                try {
219                    PackageInfo pInfo;
220                    pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES
221                            | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
222                    if (!isSignatureMatch(pInfo.signatures)) {
223                        Log.w(mTag, packageName + " resolves service " + mAction
224                                + ", but has wrong signature, ignoring");
225                        continue;
226                    }
227                } catch (NameNotFoundException e) {
228                    Log.wtf(mTag, e);
229                    continue;
230                }
231
232                // check metadata
233                int version = Integer.MIN_VALUE;
234                boolean isMultiuser = false;
235                if (rInfo.serviceInfo.metaData != null) {
236                    version = rInfo.serviceInfo.metaData.getInt(
237                            EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
238                    isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
239                }
240
241                if (version > bestVersion) {
242                    bestVersion = version;
243                    bestComponent = component;
244                    bestIsMultiuser = isMultiuser;
245                }
246            }
247
248            if (D) {
249                Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
250                        (justCheckThisPackage == null ? ""
251                                : "(" + justCheckThisPackage + ") "), rInfos.size(),
252                        (bestComponent == null ? "no new best component"
253                                : "new best component: " + bestComponent)));
254            }
255        } else {
256            if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
257        }
258
259        if (bestComponent == null) {
260            Slog.w(mTag, "Odd, no component found for service " + mAction);
261            unbindLocked();
262            return false;
263        }
264
265        final int userId = bestIsMultiuser ? UserHandle.USER_SYSTEM : mCurrentUserId;
266        final boolean alreadyBound = Objects.equals(bestComponent, mBoundComponent)
267                && bestVersion == mBoundVersion && userId == mBoundUserId;
268        if (forceRebind || !alreadyBound) {
269            unbindLocked();
270            bindToPackageLocked(bestComponent, bestVersion, userId);
271        }
272        return true;
273    }
274
275    private void unbindLocked() {
276        ComponentName component;
277        component = mBoundComponent;
278        mBoundComponent = null;
279        mBoundPackageName = null;
280        mBoundVersion = Integer.MIN_VALUE;
281        mBoundUserId = UserHandle.USER_NULL;
282        if (component != null) {
283            if (D) Log.d(mTag, "unbinding " + component);
284            mContext.unbindService(this);
285        }
286    }
287
288    private void bindToPackageLocked(ComponentName component, int version, int userId) {
289        Intent intent = new Intent(mAction);
290        intent.setComponent(component);
291        mBoundComponent = component;
292        mBoundPackageName = component.getPackageName();
293        mBoundVersion = version;
294        mBoundUserId = userId;
295        if (D) Log.d(mTag, "binding " + component + " (v" + version + ") (u" + userId + ")");
296        mContext.bindServiceAsUser(intent, this,
297                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE,
298                new UserHandle(userId));
299    }
300
301    public static boolean isSignatureMatch(Signature[] signatures,
302            List<HashSet<Signature>> sigSets) {
303        if (signatures == null) return false;
304
305        // build hashset of input to test against
306        HashSet<Signature> inputSet = new HashSet<Signature>();
307        for (Signature s : signatures) {
308            inputSet.add(s);
309        }
310
311        // test input against each of the signature sets
312        for (HashSet<Signature> referenceSet : sigSets) {
313            if (referenceSet.equals(inputSet)) {
314                return true;
315            }
316        }
317        return false;
318    }
319
320    private boolean isSignatureMatch(Signature[] signatures) {
321        return isSignatureMatch(signatures, mSignatureSets);
322    }
323
324    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
325        /**
326         * Called when package has been reinstalled
327         */
328        @Override
329        public void onPackageUpdateFinished(String packageName, int uid) {
330            synchronized (mLock) {
331                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
332                bindBestPackageLocked(null, forceRebind);
333            }
334        }
335
336        @Override
337        public void onPackageAdded(String packageName, int uid) {
338            synchronized (mLock) {
339                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
340                bindBestPackageLocked(null, forceRebind);
341            }
342        }
343
344        @Override
345        public void onPackageRemoved(String packageName, int uid) {
346            synchronized (mLock) {
347                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
348                bindBestPackageLocked(null, forceRebind);
349            }
350        }
351
352        @Override
353        public boolean onPackageChanged(String packageName, int uid, String[] components) {
354            synchronized (mLock) {
355                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
356                bindBestPackageLocked(null, forceRebind);
357            }
358            return super.onPackageChanged(packageName, uid, components);
359        }
360    };
361
362    @Override
363    public void onServiceConnected(ComponentName component, IBinder binder) {
364        synchronized (mLock) {
365            if (component.equals(mBoundComponent)) {
366                if (D) Log.d(mTag, component + " connected");
367                mBoundService = binder;
368                if (mHandler !=null && mNewServiceWork != null) {
369                    mHandler.post(mNewServiceWork);
370                }
371            } else {
372                Log.w(mTag, "unexpected onServiceConnected: " + component);
373            }
374        }
375    }
376
377    @Override
378    public void onServiceDisconnected(ComponentName component) {
379        synchronized (mLock) {
380            if (D) Log.d(mTag, component + " disconnected");
381
382            if (component.equals(mBoundComponent)) {
383                mBoundService = null;
384            }
385        }
386    }
387
388    public @Nullable String getBestPackageName() {
389        synchronized (mLock) {
390            return mBoundPackageName;
391        }
392    }
393
394    public int getBestVersion() {
395        synchronized (mLock) {
396            return mBoundVersion;
397        }
398    }
399
400    public @Nullable IBinder getBinder() {
401        synchronized (mLock) {
402            return mBoundService;
403        }
404    }
405
406    public void switchUser(int userId) {
407        synchronized (mLock) {
408            mCurrentUserId = userId;
409            bindBestPackageLocked(mServicePackageName, false);
410        }
411    }
412
413    public void unlockUser(int userId) {
414        synchronized (mLock) {
415            if (userId == mCurrentUserId) {
416                bindBestPackageLocked(mServicePackageName, false);
417            }
418        }
419    }
420}
421