Home | History | Annotate | Download | only in device
      1 /*
      2  * Copyright (C) 2013 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.tradefed.device;
     18 
     19 import com.android.ddmlib.MultiLineReceiver;
     20 import com.android.tradefed.log.LogUtil.CLog;
     21 
     22 import java.io.IOException;
     23 import java.util.HashMap;
     24 import java.util.Map;
     25 import java.util.regex.Matcher;
     26 import java.util.regex.Pattern;
     27 
     28 /**
     29  * Parser for 'adb shell dumpsys package' output.
     30  */
     31 class DumpsysPackageReceiver extends MultiLineReceiver {
     32 
     33     /** the text that marks the beginning of the hidden system packages section in output */
     34     private static final String HIDDEN_SYSTEM_PACKAGES_PREFIX = "Hidden system packages:";
     35 
     36     /** regex for marking the start of a single package's output */
     37     private static final Pattern PACKAGE_PATTERN = Pattern.compile("Package\\s\\[([\\w\\.]+)\\]");
     38 
     39     @SuppressWarnings("serial")
     40     static class ParseException extends IOException {
     41         ParseException(String msg) {
     42             super(msg);
     43         }
     44 
     45         ParseException(String msg, Throwable t) {
     46             super(msg, t);
     47         }
     48     }
     49 
     50     /**
     51      * State handling interface for parsing output. Using GoF state design pattern.
     52      */
     53     private interface ParserState {
     54         ParserState parse(String line) throws ParseException;
     55     }
     56 
     57     /**
     58      * Initial state of package parser, where its looking for start of package to parse.
     59      */
     60     private class PackagesParserState implements ParserState {
     61 
     62         /**
     63          * {@inheritDoc}
     64          */
     65         @Override
     66         public ParserState parse(String line) throws ParseException {
     67             Matcher matcher = PACKAGE_PATTERN.matcher(line);
     68             if (matcher.find()) {
     69                 String name = matcher.group(1);
     70                 return new PackageParserState(name);
     71             }
     72             return this;
     73         }
     74     }
     75 
     76     /**
     77      * Parser for a single package's data.
     78      * <p/>
     79      * Expected pattern is:
     80      * Package: [com.foo]
     81      *   key=value
     82      *   key2=value2
     83      */
     84     private class PackageParserState implements ParserState {
     85 
     86         private PackageInfo mPkgInfo;
     87 
     88         /**
     89          * @param name
     90          */
     91         public PackageParserState(String name) {
     92             mPkgInfo = new PackageInfo(name);
     93             addPackage(name, mPkgInfo);
     94         }
     95 
     96         /**
     97          * {@inheritDoc}
     98          */
     99         @Override
    100         public ParserState parse(String line) throws ParseException {
    101             // first look if we've moved on to another package
    102             Matcher matcher = PACKAGE_PATTERN.matcher(line);
    103             if (matcher.find()) {
    104                 String name = matcher.group(1);
    105                 return new PackageParserState(name);
    106             }
    107             if (line.startsWith(HIDDEN_SYSTEM_PACKAGES_PREFIX)) {
    108                 // done parsing packages, now parse hidden packages
    109                 return new HiddenPackagesParserState();
    110             }
    111             parseAttributes(line);
    112 
    113             return this;
    114         }
    115 
    116         private void parseAttributes(String line) {
    117             String[] prop = line.split("=");
    118             if (prop.length == 2) {
    119                 mPkgInfo.addAttribute(prop[0], prop[1]);
    120             } else if (prop.length > 2) {
    121                 // multiple props on one line. Split by both whitespace and =
    122                 String[] vn = line.split(" |=");
    123                 if (vn.length % 2 != 0) {
    124                     // improper format, ignore
    125                     return;
    126                 }
    127                 for (int i=0; i < vn.length; i = i + 2) {
    128                     mPkgInfo.addAttribute(vn[i], vn[i+1]);
    129                 }
    130             }
    131 
    132         }
    133     }
    134 
    135     /**
    136      * State of package parser where its looking for start of hidden packages to parse.
    137      */
    138     private class HiddenPackagesParserState implements ParserState {
    139 
    140         /**
    141          * {@inheritDoc}
    142          */
    143         @Override
    144         public ParserState parse(String line) throws ParseException {
    145             Matcher matcher = PACKAGE_PATTERN.matcher(line);
    146             if (matcher.find()) {
    147                 String name = matcher.group(1);
    148                 return new HiddenPackageParserState(name);
    149             }
    150             return this;
    151         }
    152     }
    153 
    154     /**
    155      * Parser for a single package's data
    156      */
    157     private class HiddenPackageParserState implements ParserState {
    158 
    159         private PackageInfo mPkgInfo;
    160 
    161         /**
    162          * @param name
    163          * @throws ParseException
    164          */
    165         public HiddenPackageParserState(String name) throws ParseException {
    166             mPkgInfo = mPkgInfoMap.get(name);
    167             if (mPkgInfo == null) {
    168                 throw new ParseException(String.format(
    169                         "could not find package for hidden package %s", name));
    170             }
    171             mPkgInfo.setIsUpdatedSystemApp(true);
    172         }
    173 
    174         /**
    175          * {@inheritDoc}
    176          */
    177         @Override
    178         public ParserState parse(String line) throws ParseException {
    179             Matcher matcher = PACKAGE_PATTERN.matcher(line);
    180             if (matcher.find()) {
    181                 String name = matcher.group(1);
    182                 return new HiddenPackageParserState(name);
    183             }
    184             return this;
    185         }
    186     }
    187 
    188     private Map<String, PackageInfo> mPkgInfoMap = new HashMap<String, PackageInfo>();
    189 
    190     private ParserState mCurrentState = new PackagesParserState();
    191 
    192     private boolean mCancelled = false;
    193 
    194     void addPackage(String name, PackageInfo pkgInfo) {
    195         mPkgInfoMap.put(name, pkgInfo);
    196     }
    197 
    198     /**
    199      * @return the parsed {@link PackageInfo}s as a map of package name to {@link PackageInfo}.
    200      */
    201     public Map<String, PackageInfo> getPackages() {
    202         return mPkgInfoMap;
    203     }
    204 
    205     /**
    206      * {@inheritDoc}
    207      */
    208     @Override
    209     public boolean isCancelled() {
    210         return mCancelled ;
    211     }
    212 
    213     /**
    214      * {@inheritDoc}
    215      */
    216     @Override
    217     public void processNewLines(String[] lines) {
    218         try {
    219             for (String line : lines) {
    220                 mCurrentState = mCurrentState.parse(line);
    221             }
    222         } catch (ParseException e) {
    223             CLog.e(e);
    224             mCancelled = true;
    225         }
    226     }
    227 }
    228