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