Home | History | Annotate | Download | only in server
      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 
     17 package com.android.server;
     18 
     19 import static com.android.internal.util.ArrayUtils.appendInt;
     20 
     21 import android.app.ActivityManager;
     22 import android.content.ComponentName;
     23 import android.content.pm.FeatureInfo;
     24 import android.content.pm.PackageManager;
     25 import android.os.Environment;
     26 import android.os.Process;
     27 import android.os.storage.StorageManager;
     28 import android.util.ArrayMap;
     29 import android.util.ArraySet;
     30 import android.util.Slog;
     31 import android.util.SparseArray;
     32 import android.util.Xml;
     33 
     34 import com.android.internal.util.XmlUtils;
     35 
     36 import libcore.io.IoUtils;
     37 
     38 import org.xmlpull.v1.XmlPullParser;
     39 import org.xmlpull.v1.XmlPullParserException;
     40 
     41 import java.io.File;
     42 import java.io.FileNotFoundException;
     43 import java.io.FileReader;
     44 import java.io.IOException;
     45 import java.util.ArrayList;
     46 import java.util.List;
     47 
     48 /**
     49  * Loads global system configuration info.
     50  */
     51 public class SystemConfig {
     52     static final String TAG = "SystemConfig";
     53 
     54     static SystemConfig sInstance;
     55 
     56     // permission flag, determines which types of configuration are allowed to be read
     57     private static final int ALLOW_FEATURES = 0x01;
     58     private static final int ALLOW_LIBS = 0x02;
     59     private static final int ALLOW_PERMISSIONS = 0x04;
     60     private static final int ALLOW_APP_CONFIGS = 0x08;
     61     private static final int ALLOW_ALL = ~0;
     62 
     63     // Group-ids that are given to all packages as read from etc/permissions/*.xml.
     64     int[] mGlobalGids;
     65 
     66     // These are the built-in uid -> permission mappings that were read from the
     67     // system configuration files.
     68     final SparseArray<ArraySet<String>> mSystemPermissions = new SparseArray<>();
     69 
     70     // These are the built-in shared libraries that were read from the
     71     // system configuration files.  Keys are the library names; strings are the
     72     // paths to the libraries.
     73     final ArrayMap<String, String> mSharedLibraries  = new ArrayMap<>();
     74 
     75     // These are the features this devices supports that were read from the
     76     // system configuration files.
     77     final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>();
     78 
     79     // These are the features which this device doesn't support; the OEM
     80     // partition uses these to opt-out of features from the system image.
     81     final ArraySet<String> mUnavailableFeatures = new ArraySet<>();
     82 
     83     public static final class PermissionEntry {
     84         public final String name;
     85         public int[] gids;
     86         public boolean perUser;
     87 
     88         PermissionEntry(String name, boolean perUser) {
     89             this.name = name;
     90             this.perUser = perUser;
     91         }
     92     }
     93 
     94     // These are the permission -> gid mappings that were read from the
     95     // system configuration files.
     96     final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>();
     97 
     98     // These are the packages that are white-listed to be able to run in the
     99     // background while in power save mode (but not whitelisted from device idle modes),
    100     // as read from the configuration files.
    101     final ArraySet<String> mAllowInPowerSaveExceptIdle = new ArraySet<>();
    102 
    103     // These are the packages that are white-listed to be able to run in the
    104     // background while in power save mode, as read from the configuration files.
    105     final ArraySet<String> mAllowInPowerSave = new ArraySet<>();
    106 
    107     // These are the packages that are white-listed to be able to run in the
    108     // background while in data-usage save mode, as read from the configuration files.
    109     final ArraySet<String> mAllowInDataUsageSave = new ArraySet<>();
    110 
    111     // These are the package names of apps which should be in the 'always'
    112     // URL-handling state upon factory reset.
    113     final ArraySet<String> mLinkedApps = new ArraySet<>();
    114 
    115     // These are the packages that are whitelisted to be able to run as system user
    116     final ArraySet<String> mSystemUserWhitelistedApps = new ArraySet<>();
    117 
    118     // These are the packages that should not run under system user
    119     final ArraySet<String> mSystemUserBlacklistedApps = new ArraySet<>();
    120 
    121     // These are the components that are enabled by default as VR mode listener services.
    122     final ArraySet<ComponentName> mDefaultVrComponents = new ArraySet<>();
    123 
    124     // These are the permitted backup transport service components
    125     final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
    126 
    127     // These are the packages of carrier-associated apps which should be disabled until used until
    128     // a SIM is inserted which grants carrier privileges to that carrier app.
    129     final ArrayMap<String, List<String>> mDisabledUntilUsedPreinstalledCarrierAssociatedApps =
    130             new ArrayMap<>();
    131 
    132     public static SystemConfig getInstance() {
    133         synchronized (SystemConfig.class) {
    134             if (sInstance == null) {
    135                 sInstance = new SystemConfig();
    136             }
    137             return sInstance;
    138         }
    139     }
    140 
    141     public int[] getGlobalGids() {
    142         return mGlobalGids;
    143     }
    144 
    145     public SparseArray<ArraySet<String>> getSystemPermissions() {
    146         return mSystemPermissions;
    147     }
    148 
    149     public ArrayMap<String, String> getSharedLibraries() {
    150         return mSharedLibraries;
    151     }
    152 
    153     public ArrayMap<String, FeatureInfo> getAvailableFeatures() {
    154         return mAvailableFeatures;
    155     }
    156 
    157     public ArrayMap<String, PermissionEntry> getPermissions() {
    158         return mPermissions;
    159     }
    160 
    161     public ArraySet<String> getAllowInPowerSaveExceptIdle() {
    162         return mAllowInPowerSaveExceptIdle;
    163     }
    164 
    165     public ArraySet<String> getAllowInPowerSave() {
    166         return mAllowInPowerSave;
    167     }
    168 
    169     public ArraySet<String> getAllowInDataUsageSave() {
    170         return mAllowInDataUsageSave;
    171     }
    172 
    173     public ArraySet<String> getLinkedApps() {
    174         return mLinkedApps;
    175     }
    176 
    177     public ArraySet<String> getSystemUserWhitelistedApps() {
    178         return mSystemUserWhitelistedApps;
    179     }
    180 
    181     public ArraySet<String> getSystemUserBlacklistedApps() {
    182         return mSystemUserBlacklistedApps;
    183     }
    184 
    185     public ArraySet<ComponentName> getDefaultVrComponents() {
    186         return mDefaultVrComponents;
    187     }
    188 
    189     public ArraySet<ComponentName> getBackupTransportWhitelist() {
    190         return mBackupTransportWhitelist;
    191     }
    192 
    193     public ArrayMap<String, List<String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps() {
    194         return mDisabledUntilUsedPreinstalledCarrierAssociatedApps;
    195     }
    196 
    197     SystemConfig() {
    198         // Read configuration from system
    199         readPermissions(Environment.buildPath(
    200                 Environment.getRootDirectory(), "etc", "sysconfig"), ALLOW_ALL);
    201         // Read configuration from the old permissions dir
    202         readPermissions(Environment.buildPath(
    203                 Environment.getRootDirectory(), "etc", "permissions"), ALLOW_ALL);
    204         // Allow ODM to customize system configs around libs, features and apps
    205         int odmPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_APP_CONFIGS;
    206         readPermissions(Environment.buildPath(
    207                 Environment.getOdmDirectory(), "etc", "sysconfig"), odmPermissionFlag);
    208         readPermissions(Environment.buildPath(
    209                 Environment.getOdmDirectory(), "etc", "permissions"), odmPermissionFlag);
    210         // Only allow OEM to customize features
    211         readPermissions(Environment.buildPath(
    212                 Environment.getOemDirectory(), "etc", "sysconfig"), ALLOW_FEATURES);
    213         readPermissions(Environment.buildPath(
    214                 Environment.getOemDirectory(), "etc", "permissions"), ALLOW_FEATURES);
    215     }
    216 
    217     void readPermissions(File libraryDir, int permissionFlag) {
    218         // Read permissions from given directory.
    219         if (!libraryDir.exists() || !libraryDir.isDirectory()) {
    220             if (permissionFlag == ALLOW_ALL) {
    221                 Slog.w(TAG, "No directory " + libraryDir + ", skipping");
    222             }
    223             return;
    224         }
    225         if (!libraryDir.canRead()) {
    226             Slog.w(TAG, "Directory " + libraryDir + " cannot be read");
    227             return;
    228         }
    229 
    230         // Iterate over the files in the directory and scan .xml files
    231         File platformFile = null;
    232         for (File f : libraryDir.listFiles()) {
    233             // We'll read platform.xml last
    234             if (f.getPath().endsWith("etc/permissions/platform.xml")) {
    235                 platformFile = f;
    236                 continue;
    237             }
    238 
    239             if (!f.getPath().endsWith(".xml")) {
    240                 Slog.i(TAG, "Non-xml file " + f + " in " + libraryDir + " directory, ignoring");
    241                 continue;
    242             }
    243             if (!f.canRead()) {
    244                 Slog.w(TAG, "Permissions library file " + f + " cannot be read");
    245                 continue;
    246             }
    247 
    248             readPermissionsFromXml(f, permissionFlag);
    249         }
    250 
    251         // Read platform permissions last so it will take precedence
    252         if (platformFile != null) {
    253             readPermissionsFromXml(platformFile, permissionFlag);
    254         }
    255     }
    256 
    257     private void readPermissionsFromXml(File permFile, int permissionFlag) {
    258         FileReader permReader = null;
    259         try {
    260             permReader = new FileReader(permFile);
    261         } catch (FileNotFoundException e) {
    262             Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
    263             return;
    264         }
    265 
    266         final boolean lowRam = ActivityManager.isLowRamDeviceStatic();
    267 
    268         try {
    269             XmlPullParser parser = Xml.newPullParser();
    270             parser.setInput(permReader);
    271 
    272             int type;
    273             while ((type=parser.next()) != parser.START_TAG
    274                        && type != parser.END_DOCUMENT) {
    275                 ;
    276             }
    277 
    278             if (type != parser.START_TAG) {
    279                 throw new XmlPullParserException("No start tag found");
    280             }
    281 
    282             if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
    283                 throw new XmlPullParserException("Unexpected start tag in " + permFile
    284                         + ": found " + parser.getName() + ", expected 'permissions' or 'config'");
    285             }
    286 
    287             boolean allowAll = permissionFlag == ALLOW_ALL;
    288             boolean allowLibs = (permissionFlag & ALLOW_LIBS) != 0;
    289             boolean allowFeatures = (permissionFlag & ALLOW_FEATURES) != 0;
    290             boolean allowPermissions = (permissionFlag & ALLOW_PERMISSIONS) != 0;
    291             boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
    292             while (true) {
    293                 XmlUtils.nextElement(parser);
    294                 if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
    295                     break;
    296                 }
    297 
    298                 String name = parser.getName();
    299                 if ("group".equals(name) && allowAll) {
    300                     String gidStr = parser.getAttributeValue(null, "gid");
    301                     if (gidStr != null) {
    302                         int gid = android.os.Process.getGidForName(gidStr);
    303                         mGlobalGids = appendInt(mGlobalGids, gid);
    304                     } else {
    305                         Slog.w(TAG, "<group> without gid in " + permFile + " at "
    306                                 + parser.getPositionDescription());
    307                     }
    308 
    309                     XmlUtils.skipCurrentTag(parser);
    310                     continue;
    311                 } else if ("permission".equals(name) && allowPermissions) {
    312                     String perm = parser.getAttributeValue(null, "name");
    313                     if (perm == null) {
    314                         Slog.w(TAG, "<permission> without name in " + permFile + " at "
    315                                 + parser.getPositionDescription());
    316                         XmlUtils.skipCurrentTag(parser);
    317                         continue;
    318                     }
    319                     perm = perm.intern();
    320                     readPermission(parser, perm);
    321 
    322                 } else if ("assign-permission".equals(name) && allowPermissions) {
    323                     String perm = parser.getAttributeValue(null, "name");
    324                     if (perm == null) {
    325                         Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
    326                                 + parser.getPositionDescription());
    327                         XmlUtils.skipCurrentTag(parser);
    328                         continue;
    329                     }
    330                     String uidStr = parser.getAttributeValue(null, "uid");
    331                     if (uidStr == null) {
    332                         Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
    333                                 + parser.getPositionDescription());
    334                         XmlUtils.skipCurrentTag(parser);
    335                         continue;
    336                     }
    337                     int uid = Process.getUidForName(uidStr);
    338                     if (uid < 0) {
    339                         Slog.w(TAG, "<assign-permission> with unknown uid \""
    340                                 + uidStr + "  in " + permFile + " at "
    341                                 + parser.getPositionDescription());
    342                         XmlUtils.skipCurrentTag(parser);
    343                         continue;
    344                     }
    345                     perm = perm.intern();
    346                     ArraySet<String> perms = mSystemPermissions.get(uid);
    347                     if (perms == null) {
    348                         perms = new ArraySet<String>();
    349                         mSystemPermissions.put(uid, perms);
    350                     }
    351                     perms.add(perm);
    352                     XmlUtils.skipCurrentTag(parser);
    353 
    354                 } else if ("library".equals(name) && allowLibs) {
    355                     String lname = parser.getAttributeValue(null, "name");
    356                     String lfile = parser.getAttributeValue(null, "file");
    357                     if (lname == null) {
    358                         Slog.w(TAG, "<library> without name in " + permFile + " at "
    359                                 + parser.getPositionDescription());
    360                     } else if (lfile == null) {
    361                         Slog.w(TAG, "<library> without file in " + permFile + " at "
    362                                 + parser.getPositionDescription());
    363                     } else {
    364                         //Log.i(TAG, "Got library " + lname + " in " + lfile);
    365                         mSharedLibraries.put(lname, lfile);
    366                     }
    367                     XmlUtils.skipCurrentTag(parser);
    368                     continue;
    369 
    370                 } else if ("feature".equals(name) && allowFeatures) {
    371                     String fname = parser.getAttributeValue(null, "name");
    372                     int fversion = XmlUtils.readIntAttribute(parser, "version", 0);
    373                     boolean allowed;
    374                     if (!lowRam) {
    375                         allowed = true;
    376                     } else {
    377                         String notLowRam = parser.getAttributeValue(null, "notLowRam");
    378                         allowed = !"true".equals(notLowRam);
    379                     }
    380                     if (fname == null) {
    381                         Slog.w(TAG, "<feature> without name in " + permFile + " at "
    382                                 + parser.getPositionDescription());
    383                     } else if (allowed) {
    384                         addFeature(fname, fversion);
    385                     }
    386                     XmlUtils.skipCurrentTag(parser);
    387                     continue;
    388 
    389                 } else if ("unavailable-feature".equals(name) && allowFeatures) {
    390                     String fname = parser.getAttributeValue(null, "name");
    391                     if (fname == null) {
    392                         Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
    393                                 + parser.getPositionDescription());
    394                     } else {
    395                         mUnavailableFeatures.add(fname);
    396                     }
    397                     XmlUtils.skipCurrentTag(parser);
    398                     continue;
    399 
    400                 } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
    401                     String pkgname = parser.getAttributeValue(null, "package");
    402                     if (pkgname == null) {
    403                         Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
    404                                 + permFile + " at " + parser.getPositionDescription());
    405                     } else {
    406                         mAllowInPowerSaveExceptIdle.add(pkgname);
    407                     }
    408                     XmlUtils.skipCurrentTag(parser);
    409                     continue;
    410 
    411                 } else if ("allow-in-power-save".equals(name) && allowAll) {
    412                     String pkgname = parser.getAttributeValue(null, "package");
    413                     if (pkgname == null) {
    414                         Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
    415                                 + parser.getPositionDescription());
    416                     } else {
    417                         mAllowInPowerSave.add(pkgname);
    418                     }
    419                     XmlUtils.skipCurrentTag(parser);
    420                     continue;
    421 
    422                 } else if ("allow-in-data-usage-save".equals(name) && allowAll) {
    423                     String pkgname = parser.getAttributeValue(null, "package");
    424                     if (pkgname == null) {
    425                         Slog.w(TAG, "<allow-in-data-usage-save> without package in " + permFile
    426                                 + " at " + parser.getPositionDescription());
    427                     } else {
    428                         mAllowInDataUsageSave.add(pkgname);
    429                     }
    430                     XmlUtils.skipCurrentTag(parser);
    431                     continue;
    432 
    433                 } else if ("app-link".equals(name) && allowAppConfigs) {
    434                     String pkgname = parser.getAttributeValue(null, "package");
    435                     if (pkgname == null) {
    436                         Slog.w(TAG, "<app-link> without package in " + permFile + " at "
    437                                 + parser.getPositionDescription());
    438                     } else {
    439                         mLinkedApps.add(pkgname);
    440                     }
    441                     XmlUtils.skipCurrentTag(parser);
    442                 } else if ("system-user-whitelisted-app".equals(name) && allowAppConfigs) {
    443                     String pkgname = parser.getAttributeValue(null, "package");
    444                     if (pkgname == null) {
    445                         Slog.w(TAG, "<system-user-whitelisted-app> without package in " + permFile
    446                                 + " at " + parser.getPositionDescription());
    447                     } else {
    448                         mSystemUserWhitelistedApps.add(pkgname);
    449                     }
    450                     XmlUtils.skipCurrentTag(parser);
    451                 } else if ("system-user-blacklisted-app".equals(name) && allowAppConfigs) {
    452                     String pkgname = parser.getAttributeValue(null, "package");
    453                     if (pkgname == null) {
    454                         Slog.w(TAG, "<system-user-blacklisted-app without package in " + permFile
    455                                 + " at " + parser.getPositionDescription());
    456                     } else {
    457                         mSystemUserBlacklistedApps.add(pkgname);
    458                     }
    459                     XmlUtils.skipCurrentTag(parser);
    460                 } else if ("default-enabled-vr-app".equals(name) && allowAppConfigs) {
    461                     String pkgname = parser.getAttributeValue(null, "package");
    462                     String clsname = parser.getAttributeValue(null, "class");
    463                     if (pkgname == null) {
    464                         Slog.w(TAG, "<default-enabled-vr-app without package in " + permFile
    465                                 + " at " + parser.getPositionDescription());
    466                     } else if (clsname == null) {
    467                         Slog.w(TAG, "<default-enabled-vr-app without class in " + permFile
    468                                 + " at " + parser.getPositionDescription());
    469                     } else {
    470                         mDefaultVrComponents.add(new ComponentName(pkgname, clsname));
    471                     }
    472                     XmlUtils.skipCurrentTag(parser);
    473                 } else if ("backup-transport-whitelisted-service".equals(name) && allowFeatures) {
    474                     String serviceName = parser.getAttributeValue(null, "service");
    475                     if (serviceName == null) {
    476                         Slog.w(TAG, "<backup-transport-whitelisted-service> without service in "
    477                                 + permFile + " at " + parser.getPositionDescription());
    478                     } else {
    479                         ComponentName cn = ComponentName.unflattenFromString(serviceName);
    480                         if (cn == null) {
    481                             Slog.w(TAG,
    482                                     "<backup-transport-whitelisted-service> with invalid service name "
    483                                     + serviceName + " in "+ permFile
    484                                     + " at " + parser.getPositionDescription());
    485                         } else {
    486                             mBackupTransportWhitelist.add(cn);
    487                         }
    488                     }
    489                     XmlUtils.skipCurrentTag(parser);
    490                 } else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name)
    491                         && allowAppConfigs) {
    492                     String pkgname = parser.getAttributeValue(null, "package");
    493                     String carrierPkgname = parser.getAttributeValue(null, "carrierAppPackage");
    494                     if (pkgname == null || carrierPkgname == null) {
    495                         Slog.w(TAG, "<disabled-until-used-preinstalled-carrier-associated-app"
    496                                 + " without package or carrierAppPackage in " + permFile + " at "
    497                                 + parser.getPositionDescription());
    498                     } else {
    499                         List<String> associatedPkgs =
    500                                 mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get(
    501                                         carrierPkgname);
    502                         if (associatedPkgs == null) {
    503                             associatedPkgs = new ArrayList<>();
    504                             mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put(
    505                                     carrierPkgname, associatedPkgs);
    506                         }
    507                         associatedPkgs.add(pkgname);
    508                     }
    509                     XmlUtils.skipCurrentTag(parser);
    510                 } else {
    511                     XmlUtils.skipCurrentTag(parser);
    512                     continue;
    513                 }
    514             }
    515         } catch (XmlPullParserException e) {
    516             Slog.w(TAG, "Got exception parsing permissions.", e);
    517         } catch (IOException e) {
    518             Slog.w(TAG, "Got exception parsing permissions.", e);
    519         } finally {
    520             IoUtils.closeQuietly(permReader);
    521         }
    522 
    523         // Some devices can be field-converted to FBE, so offer to splice in
    524         // those features if not already defined by the static config
    525         if (StorageManager.isFileEncryptedNativeOnly()) {
    526             addFeature(PackageManager.FEATURE_FILE_BASED_ENCRYPTION, 0);
    527             addFeature(PackageManager.FEATURE_SECURELY_REMOVES_USERS, 0);
    528         }
    529 
    530         for (String featureName : mUnavailableFeatures) {
    531             removeFeature(featureName);
    532         }
    533     }
    534 
    535     private void addFeature(String name, int version) {
    536         FeatureInfo fi = mAvailableFeatures.get(name);
    537         if (fi == null) {
    538             fi = new FeatureInfo();
    539             fi.name = name;
    540             fi.version = version;
    541             mAvailableFeatures.put(name, fi);
    542         } else {
    543             fi.version = Math.max(fi.version, version);
    544         }
    545     }
    546 
    547     private void removeFeature(String name) {
    548         if (mAvailableFeatures.remove(name) != null) {
    549             Slog.d(TAG, "Removed unavailable feature " + name);
    550         }
    551     }
    552 
    553     void readPermission(XmlPullParser parser, String name)
    554             throws IOException, XmlPullParserException {
    555         if (mPermissions.containsKey(name)) {
    556             throw new IllegalStateException("Duplicate permission definition for " + name);
    557         }
    558 
    559         final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false);
    560         final PermissionEntry perm = new PermissionEntry(name, perUser);
    561         mPermissions.put(name, perm);
    562 
    563         int outerDepth = parser.getDepth();
    564         int type;
    565         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    566                && (type != XmlPullParser.END_TAG
    567                        || parser.getDepth() > outerDepth)) {
    568             if (type == XmlPullParser.END_TAG
    569                     || type == XmlPullParser.TEXT) {
    570                 continue;
    571             }
    572 
    573             String tagName = parser.getName();
    574             if ("group".equals(tagName)) {
    575                 String gidStr = parser.getAttributeValue(null, "gid");
    576                 if (gidStr != null) {
    577                     int gid = Process.getGidForName(gidStr);
    578                     perm.gids = appendInt(perm.gids, gid);
    579                 } else {
    580                     Slog.w(TAG, "<group> without gid at "
    581                             + parser.getPositionDescription());
    582                 }
    583             }
    584             XmlUtils.skipCurrentTag(parser);
    585         }
    586     }
    587 }
    588