1/*
2 * Copyright (C) 2011 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.am;
18
19import android.content.ComponentName;
20import android.os.Binder;
21import android.os.RemoteException;
22import android.os.UserHandle;
23import android.util.Slog;
24import android.util.SparseArray;
25import com.android.internal.os.TransferPipe;
26
27import java.io.FileDescriptor;
28import java.io.IOException;
29import java.io.PrintWriter;
30import java.util.ArrayList;
31import java.util.HashMap;
32import java.util.Iterator;
33import java.util.Map;
34import java.util.Set;
35
36/**
37 * Keeps track of content providers by authority (name) and class. It separates the mapping by
38 * user and ones that are not user-specific (system providers).
39 */
40public final class ProviderMap {
41
42    private static final String TAG = "ProviderMap";
43
44    private static final boolean DBG = false;
45
46    private final ActivityManagerService mAm;
47
48    private final HashMap<String, ContentProviderRecord> mSingletonByName
49            = new HashMap<String, ContentProviderRecord>();
50    private final HashMap<ComponentName, ContentProviderRecord> mSingletonByClass
51            = new HashMap<ComponentName, ContentProviderRecord>();
52
53    private final SparseArray<HashMap<String, ContentProviderRecord>> mProvidersByNamePerUser
54            = new SparseArray<HashMap<String, ContentProviderRecord>>();
55    private final SparseArray<HashMap<ComponentName, ContentProviderRecord>> mProvidersByClassPerUser
56            = new SparseArray<HashMap<ComponentName, ContentProviderRecord>>();
57
58    ProviderMap(ActivityManagerService am) {
59        mAm = am;
60    }
61
62    ContentProviderRecord getProviderByName(String name) {
63        return getProviderByName(name, -1);
64    }
65
66    ContentProviderRecord getProviderByName(String name, int userId) {
67        if (DBG) {
68            Slog.i(TAG, "getProviderByName: " + name + " , callingUid = " + Binder.getCallingUid());
69        }
70        // Try to find it in the global list
71        ContentProviderRecord record = mSingletonByName.get(name);
72        if (record != null) {
73            return record;
74        }
75
76        // Check the current user's list
77        return getProvidersByName(userId).get(name);
78    }
79
80    ContentProviderRecord getProviderByClass(ComponentName name) {
81        return getProviderByClass(name, -1);
82    }
83
84    ContentProviderRecord getProviderByClass(ComponentName name, int userId) {
85        if (DBG) {
86            Slog.i(TAG, "getProviderByClass: " + name + ", callingUid = " + Binder.getCallingUid());
87        }
88        // Try to find it in the global list
89        ContentProviderRecord record = mSingletonByClass.get(name);
90        if (record != null) {
91            return record;
92        }
93
94        // Check the current user's list
95        return getProvidersByClass(userId).get(name);
96    }
97
98    void putProviderByName(String name, ContentProviderRecord record) {
99        if (DBG) {
100            Slog.i(TAG, "putProviderByName: " + name + " , callingUid = " + Binder.getCallingUid()
101                + ", record uid = " + record.appInfo.uid);
102        }
103        if (record.singleton) {
104            mSingletonByName.put(name, record);
105        } else {
106            final int userId = UserHandle.getUserId(record.appInfo.uid);
107            getProvidersByName(userId).put(name, record);
108        }
109    }
110
111    void putProviderByClass(ComponentName name, ContentProviderRecord record) {
112        if (DBG) {
113            Slog.i(TAG, "putProviderByClass: " + name + " , callingUid = " + Binder.getCallingUid()
114                + ", record uid = " + record.appInfo.uid);
115        }
116        if (record.singleton) {
117            mSingletonByClass.put(name, record);
118        } else {
119            final int userId = UserHandle.getUserId(record.appInfo.uid);
120            getProvidersByClass(userId).put(name, record);
121        }
122    }
123
124    void removeProviderByName(String name, int userId) {
125        if (mSingletonByName.containsKey(name)) {
126            if (DBG)
127                Slog.i(TAG, "Removing from globalByName name=" + name);
128            mSingletonByName.remove(name);
129        } else {
130            if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
131            if (DBG)
132                Slog.i(TAG,
133                        "Removing from providersByName name=" + name + " user=" + userId);
134            HashMap<String, ContentProviderRecord> map = getProvidersByName(userId);
135            // map returned by getProvidersByName wouldn't be null
136            map.remove(name);
137            if (map.size() == 0) {
138                mProvidersByNamePerUser.remove(userId);
139            }
140        }
141    }
142
143    void removeProviderByClass(ComponentName name, int userId) {
144        if (mSingletonByClass.containsKey(name)) {
145            if (DBG)
146                Slog.i(TAG, "Removing from globalByClass name=" + name);
147            mSingletonByClass.remove(name);
148        } else {
149            if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
150            if (DBG)
151                Slog.i(TAG,
152                        "Removing from providersByClass name=" + name + " user=" + userId);
153            HashMap<ComponentName, ContentProviderRecord> map = getProvidersByClass(userId);
154            // map returned by getProvidersByClass wouldn't be null
155            map.remove(name);
156            if (map.size() == 0) {
157                mProvidersByClassPerUser.remove(userId);
158            }
159        }
160    }
161
162    private HashMap<String, ContentProviderRecord> getProvidersByName(int userId) {
163        if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
164        final HashMap<String, ContentProviderRecord> map = mProvidersByNamePerUser.get(userId);
165        if (map == null) {
166            HashMap<String, ContentProviderRecord> newMap = new HashMap<String, ContentProviderRecord>();
167            mProvidersByNamePerUser.put(userId, newMap);
168            return newMap;
169        } else {
170            return map;
171        }
172    }
173
174    HashMap<ComponentName, ContentProviderRecord> getProvidersByClass(int userId) {
175        if (userId < 0) throw new IllegalArgumentException("Bad user " + userId);
176        final HashMap<ComponentName, ContentProviderRecord> map
177                = mProvidersByClassPerUser.get(userId);
178        if (map == null) {
179            HashMap<ComponentName, ContentProviderRecord> newMap
180                    = new HashMap<ComponentName, ContentProviderRecord>();
181            mProvidersByClassPerUser.put(userId, newMap);
182            return newMap;
183        } else {
184            return map;
185        }
186    }
187
188    private boolean collectPackageProvidersLocked(String packageName,
189            Set<String> filterByClasses, boolean doit, boolean evenPersistent,
190            HashMap<ComponentName, ContentProviderRecord> providers,
191            ArrayList<ContentProviderRecord> result) {
192        boolean didSomething = false;
193        for (ContentProviderRecord provider : providers.values()) {
194            final boolean sameComponent = packageName == null
195                    || (provider.info.packageName.equals(packageName)
196                        && (filterByClasses == null
197                            || filterByClasses.contains(provider.name.getClassName())));
198            if (sameComponent
199                    && (provider.proc == null || evenPersistent || !provider.proc.persistent)) {
200                if (!doit) {
201                    return true;
202                }
203                didSomething = true;
204                result.add(provider);
205            }
206        }
207        return didSomething;
208    }
209
210    boolean collectPackageProvidersLocked(String packageName, Set<String> filterByClasses,
211            boolean doit, boolean evenPersistent, int userId,
212            ArrayList<ContentProviderRecord> result) {
213        boolean didSomething = false;
214        if (userId == UserHandle.USER_ALL || userId == UserHandle.USER_SYSTEM) {
215            didSomething = collectPackageProvidersLocked(packageName, filterByClasses,
216                    doit, evenPersistent, mSingletonByClass, result);
217        }
218        if (!doit && didSomething) {
219            return true;
220        }
221        if (userId == UserHandle.USER_ALL) {
222            for (int i = 0; i < mProvidersByClassPerUser.size(); i++) {
223                if (collectPackageProvidersLocked(packageName, filterByClasses,
224                        doit, evenPersistent, mProvidersByClassPerUser.valueAt(i), result)) {
225                    if (!doit) {
226                        return true;
227                    }
228                    didSomething = true;
229                }
230            }
231        } else {
232            HashMap<ComponentName, ContentProviderRecord> items
233                    = getProvidersByClass(userId);
234            if (items != null) {
235                didSomething |= collectPackageProvidersLocked(packageName, filterByClasses,
236                        doit, evenPersistent, items, result);
237            }
238        }
239        return didSomething;
240    }
241
242    private boolean dumpProvidersByClassLocked(PrintWriter pw, boolean dumpAll, String dumpPackage,
243            String header, boolean needSep, HashMap<ComponentName, ContentProviderRecord> map) {
244        Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it = map.entrySet().iterator();
245        boolean written = false;
246        while (it.hasNext()) {
247            Map.Entry<ComponentName, ContentProviderRecord> e = it.next();
248            ContentProviderRecord r = e.getValue();
249            if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
250                continue;
251            }
252            if (needSep) {
253                pw.println("");
254                needSep = false;
255            }
256            if (header != null) {
257                pw.println(header);
258                header = null;
259            }
260            written = true;
261            pw.print("  * ");
262            pw.println(r);
263            r.dump(pw, "    ", dumpAll);
264        }
265        return written;
266    }
267
268    private boolean dumpProvidersByNameLocked(PrintWriter pw, String dumpPackage,
269            String header, boolean needSep, HashMap<String, ContentProviderRecord> map) {
270        Iterator<Map.Entry<String, ContentProviderRecord>> it = map.entrySet().iterator();
271        boolean written = false;
272        while (it.hasNext()) {
273            Map.Entry<String, ContentProviderRecord> e = it.next();
274            ContentProviderRecord r = e.getValue();
275            if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) {
276                continue;
277            }
278            if (needSep) {
279                pw.println("");
280                needSep = false;
281            }
282            if (header != null) {
283                pw.println(header);
284                header = null;
285            }
286            written = true;
287            pw.print("  ");
288            pw.print(e.getKey());
289            pw.print(": ");
290            pw.println(r.toShortString());
291        }
292        return written;
293    }
294
295    boolean dumpProvidersLocked(PrintWriter pw, boolean dumpAll, String dumpPackage) {
296        boolean needSep = false;
297
298        if (mSingletonByClass.size() > 0) {
299            needSep |= dumpProvidersByClassLocked(pw, dumpAll, dumpPackage,
300                    "  Published single-user content providers (by class):", needSep,
301                    mSingletonByClass);
302        }
303
304        for (int i = 0; i < mProvidersByClassPerUser.size(); i++) {
305            HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i);
306            needSep |= dumpProvidersByClassLocked(pw, dumpAll, dumpPackage,
307                    "  Published user " + mProvidersByClassPerUser.keyAt(i)
308                            + " content providers (by class):", needSep, map);
309        }
310
311        if (dumpAll) {
312            needSep |= dumpProvidersByNameLocked(pw, dumpPackage,
313                    "  Single-user authority to provider mappings:", needSep, mSingletonByName);
314
315            for (int i = 0; i < mProvidersByNamePerUser.size(); i++) {
316                needSep |= dumpProvidersByNameLocked(pw, dumpPackage,
317                        "  User " + mProvidersByNamePerUser.keyAt(i)
318                                + " authority to provider mappings:", needSep,
319                        mProvidersByNamePerUser.valueAt(i));
320            }
321        }
322        return needSep;
323    }
324
325    protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
326            int opti, boolean dumpAll) {
327        ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>();
328        ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
329
330        synchronized (mAm) {
331            allProviders.addAll(mSingletonByClass.values());
332            for (int i=0; i<mProvidersByClassPerUser.size(); i++) {
333                allProviders.addAll(mProvidersByClassPerUser.valueAt(i).values());
334            }
335
336            if ("all".equals(name)) {
337                providers.addAll(allProviders);
338            } else {
339                ComponentName componentName = name != null
340                        ? ComponentName.unflattenFromString(name) : null;
341                int objectId = 0;
342                if (componentName == null) {
343                    // Not a '/' separated full component name; maybe an object ID?
344                    try {
345                        objectId = Integer.parseInt(name, 16);
346                        name = null;
347                        componentName = null;
348                    } catch (RuntimeException e) {
349                    }
350                }
351
352                for (int i=0; i<allProviders.size(); i++) {
353                    ContentProviderRecord r1 = allProviders.get(i);
354                    if (componentName != null) {
355                        if (r1.name.equals(componentName)) {
356                            providers.add(r1);
357                        }
358                    } else if (name != null) {
359                        if (r1.name.flattenToString().contains(name)) {
360                            providers.add(r1);
361                        }
362                    } else if (System.identityHashCode(r1) == objectId) {
363                        providers.add(r1);
364                    }
365                }
366            }
367        }
368
369        if (providers.size() <= 0) {
370            return false;
371        }
372
373        boolean needSep = false;
374        for (int i=0; i<providers.size(); i++) {
375            if (needSep) {
376                pw.println();
377            }
378            needSep = true;
379            dumpProvider("", fd, pw, providers.get(i), args, dumpAll);
380        }
381        return true;
382    }
383
384    /**
385     * Before invoking IApplicationThread.dumpProvider(), print meta information to the print
386     * writer and handle passed flags.
387     */
388    private void dumpProvider(String prefix, FileDescriptor fd, PrintWriter pw,
389            final ContentProviderRecord r, String[] args, boolean dumpAll) {
390        for (String s: args) {
391            if (!dumpAll && s.contains("--proto")) {
392                if (r.proc != null && r.proc.thread != null) {
393                    dumpToTransferPipe(null , fd, pw, r, args);
394                }
395                return;
396            }
397        }
398        String innerPrefix = prefix + "  ";
399        synchronized (mAm) {
400            pw.print(prefix); pw.print("PROVIDER ");
401            pw.print(r);
402            pw.print(" pid=");
403            if (r.proc != null) {
404                pw.println(r.proc.pid);
405            } else {
406                pw.println("(not running)");
407            }
408            if (dumpAll) {
409                r.dump(pw, innerPrefix, true);
410            }
411        }
412        if (r.proc != null && r.proc.thread != null) {
413            pw.println("    Client:");
414            pw.flush();
415            dumpToTransferPipe("      ", fd, pw, r, args);
416        }
417    }
418
419    /**
420     * Invokes IApplicationThread.dumpProvider() on the thread of the specified provider without
421     * any meta string (e.g., provider info, indentation) written to the file descriptor.
422     */
423    private void dumpToTransferPipe(String prefix, FileDescriptor fd, PrintWriter pw,
424            final ContentProviderRecord r, String[] args) {
425        try {
426            TransferPipe tp = new TransferPipe();
427            try {
428                r.proc.thread.dumpProvider(
429                    tp.getWriteFd(), r.provider.asBinder(), args);
430                tp.setBufferPrefix(prefix);
431                // Short timeout, since blocking here can
432                // deadlock with the application.
433                tp.go(fd, 2000);
434            } finally {
435                tp.kill();
436            }
437        } catch (IOException ex) {
438            pw.println("      Failure while dumping the provider: " + ex);
439        } catch (RemoteException ex) {
440            pw.println("      Got a RemoteException while dumping the service");
441        }
442    }
443}
444