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