1/*
2 * Copyright (C) 2014 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.app.ActivityManager;
20import android.content.pm.FeatureInfo;
21import android.os.*;
22import android.os.Process;
23import android.util.ArrayMap;
24import android.util.ArraySet;
25import android.util.Slog;
26import android.util.SparseArray;
27import android.util.Xml;
28
29import libcore.io.IoUtils;
30
31import com.android.internal.util.XmlUtils;
32
33import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35
36import java.io.File;
37import java.io.FileNotFoundException;
38import java.io.FileReader;
39import java.io.IOException;
40
41import static com.android.internal.util.ArrayUtils.appendInt;
42
43/**
44 * Loads global system configuration info.
45 */
46public class SystemConfig {
47    static final String TAG = "SystemConfig";
48
49    static SystemConfig sInstance;
50
51    // Group-ids that are given to all packages as read from etc/permissions/*.xml.
52    int[] mGlobalGids;
53
54    // These are the built-in uid -> permission mappings that were read from the
55    // system configuration files.
56    final SparseArray<ArraySet<String>> mSystemPermissions = new SparseArray<>();
57
58    // These are the built-in shared libraries that were read from the
59    // system configuration files.  Keys are the library names; strings are the
60    // paths to the libraries.
61    final ArrayMap<String, String> mSharedLibraries  = new ArrayMap<>();
62
63    // These are the features this devices supports that were read from the
64    // system configuration files.
65    final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();
66
67    // These are the features which this device doesn't support; the OEM
68    // partition uses these to opt-out of features from the system image.
69    final ArraySet<String> mUnavailableFeatures = new ArraySet<>();
70
71    public static final class PermissionEntry {
72        public final String name;
73        public int[] gids;
74        public boolean perUser;
75
76        PermissionEntry(String name, boolean perUser) {
77            this.name = name;
78            this.perUser = perUser;
79        }
80    }
81
82    // These are the permission -> gid mappings that were read from the
83    // system configuration files.
84    final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();
85
86    // These are the packages that are white-listed to be able to run in the
87    // background while in power save mode (but not whitelisted from device idle modes),
88    // as read from the configuration files.
89    final ArraySet<String> mAllowInPowerSaveExceptIdle = new ArraySet<>();
90
91    // These are the packages that are white-listed to be able to run in the
92    // background while in power save mode, as read from the configuration files.
93    final ArraySet<String> mAllowInPowerSave = new ArraySet<>();
94
95    // These are the app package names that should not allow IME switching.
96    final ArraySet<String> mFixedImeApps = new ArraySet<>();
97
98    // These are the package names of apps which should be in the 'always'
99    // URL-handling state upon factory reset.
100    final ArraySet<String> mLinkedApps = new ArraySet<>();
101
102    public static SystemConfig getInstance() {
103        synchronized (SystemConfig.class) {
104            if (sInstance == null) {
105                sInstance = new SystemConfig();
106            }
107            return sInstance;
108        }
109    }
110
111    public int[] getGlobalGids() {
112        return mGlobalGids;
113    }
114
115    public SparseArray<ArraySet<String>> getSystemPermissions() {
116        return mSystemPermissions;
117    }
118
119    public ArrayMap<String, String> getSharedLibraries() {
120        return mSharedLibraries;
121    }
122
123    public ArrayMap<String, FeatureInfo> getAvailableFeatures() {
124        return mAvailableFeatures;
125    }
126
127    public ArrayMap<String, PermissionEntry> getPermissions() {
128        return mPermissions;
129    }
130
131    public ArraySet<String> getAllowInPowerSaveExceptIdle() {
132        return mAllowInPowerSaveExceptIdle;
133    }
134
135    public ArraySet<String> getAllowInPowerSave() {
136        return mAllowInPowerSave;
137    }
138
139    public ArraySet<String> getFixedImeApps() {
140        return mFixedImeApps;
141    }
142
143    public ArraySet<String> getLinkedApps() {
144        return mLinkedApps;
145    }
146
147    SystemConfig() {
148        // Read configuration from system
149        readPermissions(Environment.buildPath(
150                Environment.getRootDirectory(), "etc", "sysconfig"), false);
151        // Read configuration from the old permissions dir
152        readPermissions(Environment.buildPath(
153                Environment.getRootDirectory(), "etc", "permissions"), false);
154        // Only read features from OEM config
155        readPermissions(Environment.buildPath(
156                Environment.getOemDirectory(), "etc", "sysconfig"), true);
157        readPermissions(Environment.buildPath(
158                Environment.getOemDirectory(), "etc", "permissions"), true);
159    }
160
161    void readPermissions(File libraryDir, boolean onlyFeatures) {
162        // Read permissions from given directory.
163        if (!libraryDir.exists() || !libraryDir.isDirectory()) {
164            if (!onlyFeatures) {
165                Slog.w(TAG, "No directory " + libraryDir + ", skipping");
166            }
167            return;
168        }
169        if (!libraryDir.canRead()) {
170            Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
171            return;
172        }
173
174        // Iterate over the files in the directory and scan .xml files
175        File platformFile = null;
176        for (File f : libraryDir.listFiles()) {
177            // We'll read platform.xml last
178            if (f.getPath().endsWith("etc/permissions/platform.xml")) {
179                platformFile = f;
180                continue;
181            }
182
183            if (!f.getPath().endsWith(".xml")) {
184                Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
185                continue;
186            }
187            if (!f.canRead()) {
188                Slog.w(TAG, "Permissions library file " + f + " cannot be read");
189                continue;
190            }
191
192            readPermissionsFromXml(f, onlyFeatures);
193        }
194
195        // Read platform permissions last so it will take precedence
196        if (platformFile != null) {
197            readPermissionsFromXml(platformFile, onlyFeatures);
198        }
199    }
200
201    private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
202        FileReader permReader = null;
203        try {
204            permReader = new FileReader(permFile);
205        } catch (FileNotFoundException e) {
206            Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
207            return;
208        }
209
210        final boolean lowRam = ActivityManager.isLowRamDeviceStatic();
211
212        try {
213            XmlPullParser parser = Xml.newPullParser();
214            parser.setInput(permReader);
215
216            int type;
217            while ((type=parser.next()) != parser.START_TAG
218                       && type != parser.END_DOCUMENT) {
219                ;
220            }
221
222            if (type != parser.START_TAG) {
223                throw new XmlPullParserException("No start tag found");
224            }
225
226            if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
227                throw new XmlPullParserException("Unexpected start tag in " + permFile
228                        + ": found " + parser.getName() + ", expected 'permissions' or 'config'");
229            }
230
231            while (true) {
232                XmlUtils.nextElement(parser);
233                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
234                    break;
235                }
236
237                String name = parser.getName();
238                if ("group".equals(name) && !onlyFeatures) {
239                    String gidStr = parser.getAttributeValue(null, "gid");
240                    if (gidStr != null) {
241                        int gid = android.os.Process.getGidForName(gidStr);
242                        mGlobalGids = appendInt(mGlobalGids, gid);
243                    } else {
244                        Slog.w(TAG, "<group> without gid in " + permFile + " at "
245                                + parser.getPositionDescription());
246                    }
247
248                    XmlUtils.skipCurrentTag(parser);
249                    continue;
250                } else if ("permission".equals(name) && !onlyFeatures) {
251                    String perm = parser.getAttributeValue(null, "name");
252                    if (perm == null) {
253                        Slog.w(TAG, "<permission> without name in " + permFile + " at "
254                                + parser.getPositionDescription());
255                        XmlUtils.skipCurrentTag(parser);
256                        continue;
257                    }
258                    perm = perm.intern();
259                    readPermission(parser, perm);
260
261                } else if ("assign-permission".equals(name) && !onlyFeatures) {
262                    String perm = parser.getAttributeValue(null, "name");
263                    if (perm == null) {
264                        Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
265                                + parser.getPositionDescription());
266                        XmlUtils.skipCurrentTag(parser);
267                        continue;
268                    }
269                    String uidStr = parser.getAttributeValue(null, "uid");
270                    if (uidStr == null) {
271                        Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
272                                + parser.getPositionDescription());
273                        XmlUtils.skipCurrentTag(parser);
274                        continue;
275                    }
276                    int uid = Process.getUidForName(uidStr);
277                    if (uid < 0) {
278                        Slog.w(TAG, "<assign-permission> with unknown uid \""
279                                + uidStr + "  in " + permFile + " at "
280                                + parser.getPositionDescription());
281                        XmlUtils.skipCurrentTag(parser);
282                        continue;
283                    }
284                    perm = perm.intern();
285                    ArraySet<String> perms = mSystemPermissions.get(uid);
286                    if (perms == null) {
287                        perms = new ArraySet<String>();
288                        mSystemPermissions.put(uid, perms);
289                    }
290                    perms.add(perm);
291                    XmlUtils.skipCurrentTag(parser);
292
293                } else if ("library".equals(name) && !onlyFeatures) {
294                    String lname = parser.getAttributeValue(null, "name");
295                    String lfile = parser.getAttributeValue(null, "file");
296                    if (lname == null) {
297                        Slog.w(TAG, "<library> without name in " + permFile + " at "
298                                + parser.getPositionDescription());
299                    } else if (lfile == null) {
300                        Slog.w(TAG, "<library> without file in " + permFile + " at "
301                                + parser.getPositionDescription());
302                    } else {
303                        //Log.i(TAG, "Got library " + lname + " in " + lfile);
304                        mSharedLibraries.put(lname, lfile);
305                    }
306                    XmlUtils.skipCurrentTag(parser);
307                    continue;
308
309                } else if ("feature".equals(name)) {
310                    String fname = parser.getAttributeValue(null, "name");
311                    boolean allowed;
312                    if (!lowRam) {
313                        allowed = true;
314                    } else {
315                        String notLowRam = parser.getAttributeValue(null, "notLowRam");
316                        allowed = !"true".equals(notLowRam);
317                    }
318                    if (fname == null) {
319                        Slog.w(TAG, "<feature> without name in " + permFile + " at "
320                                + parser.getPositionDescription());
321                    } else if (allowed) {
322                        //Log.i(TAG, "Got feature " + fname);
323                        FeatureInfo fi = new FeatureInfo();
324                        fi.name = fname;
325                        mAvailableFeatures.put(fname, fi);
326                    }
327                    XmlUtils.skipCurrentTag(parser);
328                    continue;
329
330                } else if ("unavailable-feature".equals(name)) {
331                    String fname = parser.getAttributeValue(null, "name");
332                    if (fname == null) {
333                        Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
334                                + parser.getPositionDescription());
335                    } else {
336                        mUnavailableFeatures.add(fname);
337                    }
338                    XmlUtils.skipCurrentTag(parser);
339                    continue;
340
341                } else if ("allow-in-power-save-except-idle".equals(name) && !onlyFeatures) {
342                    String pkgname = parser.getAttributeValue(null, "package");
343                    if (pkgname == null) {
344                        Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
345                                + permFile + " at " + parser.getPositionDescription());
346                    } else {
347                        mAllowInPowerSaveExceptIdle.add(pkgname);
348                    }
349                    XmlUtils.skipCurrentTag(parser);
350                    continue;
351
352                } else if ("allow-in-power-save".equals(name) && !onlyFeatures) {
353                    String pkgname = parser.getAttributeValue(null, "package");
354                    if (pkgname == null) {
355                        Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
356                                + parser.getPositionDescription());
357                    } else {
358                        mAllowInPowerSave.add(pkgname);
359                    }
360                    XmlUtils.skipCurrentTag(parser);
361                    continue;
362
363                } else if ("fixed-ime-app".equals(name) && !onlyFeatures) {
364                    String pkgname = parser.getAttributeValue(null, "package");
365                    if (pkgname == null) {
366                        Slog.w(TAG, "<fixed-ime-app> without package in " + permFile + " at "
367                                + parser.getPositionDescription());
368                    } else {
369                        mFixedImeApps.add(pkgname);
370                    }
371                    XmlUtils.skipCurrentTag(parser);
372                    continue;
373
374                } else if ("app-link".equals(name)) {
375                    String pkgname = parser.getAttributeValue(null, "package");
376                    if (pkgname == null) {
377                        Slog.w(TAG, "<app-link> without package in " + permFile + " at "
378                                + parser.getPositionDescription());
379                    } else {
380                        mLinkedApps.add(pkgname);
381                    }
382                    XmlUtils.skipCurrentTag(parser);
383
384                } else {
385                    XmlUtils.skipCurrentTag(parser);
386                    continue;
387                }
388            }
389        } catch (XmlPullParserException e) {
390            Slog.w(TAG, "Got exception parsing permissions.", e);
391        } catch (IOException e) {
392            Slog.w(TAG, "Got exception parsing permissions.", e);
393        } finally {
394            IoUtils.closeQuietly(permReader);
395        }
396
397        for (String fname : mUnavailableFeatures) {
398            if (mAvailableFeatures.remove(fname) != null) {
399                Slog.d(TAG, "Removed unavailable feature " + fname);
400            }
401        }
402    }
403
404    void readPermission(XmlPullParser parser, String name)
405            throws IOException, XmlPullParserException {
406        if (mPermissions.containsKey(name)) {
407            throw new IllegalStateException("Duplicate permission definition for " + name);
408        }
409
410        final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false);
411        final PermissionEntry perm = new PermissionEntry(name, perUser);
412        mPermissions.put(name, perm);
413
414        int outerDepth = parser.getDepth();
415        int type;
416        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
417               && (type != XmlPullParser.END_TAG
418                       || parser.getDepth() > outerDepth)) {
419            if (type == XmlPullParser.END_TAG
420                    || type == XmlPullParser.TEXT) {
421                continue;
422            }
423
424            String tagName = parser.getName();
425            if ("group".equals(tagName)) {
426                String gidStr = parser.getAttributeValue(null, "gid");
427                if (gidStr != null) {
428                    int gid = Process.getGidForName(gidStr);
429                    perm.gids = appendInt(perm.gids, gid);
430                } else {
431                    Slog.w(TAG, "<group> without gid at "
432                            + parser.getPositionDescription());
433                }
434            }
435            XmlUtils.skipCurrentTag(parser);
436        }
437    }
438}
439