2018-04-09应用管理

PM扫描安装过程.png
PackageParser解析流程.png
PackageParser类关系图.png
PackageManagerServiceBinder图.png

应用管理

本文主要是分析的是“设置”--> "应用"中的应用管理中的应用的运行时权限和默认应用配置等。
分析了应用权限管理的整个流程 和 默认应用程序的设置

一、应用运行时权限管理

运行时权限授权.png

Settings --> 应用 --> 配置应用(设置中)--> 应用所需权限
界面:
Activity:
com.android.packageinstaller/.permission.ui.ManagePermissionsActivity
Fragment:
com.android.packageinstaller.permission.ui.handheld.ManagePermissionsFragment
ManagePermissionsFragment有两个,其中一个是针对电视设备的,这里调用的是handheld目录下的文件
界面很简单,主要利用listView空间来实现。这里重点关注下这里的数据

    //com.android.packageinstaller.permission.ui.handheld.ManagePermissionsFragment.java
    
    private PermissionGroups mPermissions;
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
         ****
         //mPermissions的由来
        mPermissions = new PermissionGroups(getActivity(), getLoaderManager(), this);
    }
    private void updatePermissionsUi() {
        Context context = getActivity();
        if (context == null) {
            return;
        }

        //主要是这个 groups变量,为listView提供数据
        List groups = mPermissions.getGroups();
        PreferenceScreen screen = getPreferenceScreen();
        if (screen == null) {
            screen = getPreferenceManager().createPreferenceScreen(getActivity());
            setPreferenceScreen(screen);
        }

listView的数据主要是由,PermissionGroups对象的getGroups方法获得List , 处理后然后给listView的litem赋值。
下面看下PermissionGroups怎么构造的数据。
mPermissions

    //PackageInstaller/src/com/android/packageinstaller/permission/model/PermissionGroups.java   
    public final class PermissionGroups implements LoaderCallbacks> {
        *****
        //构造器
        public PermissionGroups(Context context, LoaderManager loaderManager,
            PermissionsGroupsChangeCallback callback) {
        mContext = context;
        mLoaderManager = loaderManager;
        mCallback = callback;
         }
         
             @Override
    public void onLoadFinished(Loader> loader,
            List groups) {
        if (mGroups.equals(groups)) {
            return;
        }
        mGroups.clear();
        mGroups.addAll(groups);
        mCallback.onPermissionGroupsChanged();
     }

    private static final class PermissionsLoader extends AsyncTaskLoader> {

        public PermissionsLoader(Context context) {
            super(context);
        }

        @Override
        public List loadInBackground() {
             ****
            PackageManager packageManager = getContext().getPackageManager();
            List groupInfos = packageManager.getAllPermissionGroups(0);        

这里使用了Android提供的处理异步任务的方法,AsyncTaskLoader这里简单介绍下
loadInBackground()方法执行的耗时操作,通过packageManagerService来获取List groupInfos ,
获取完成后会回调 LoaderCallbacks接口中的 onLoadFinished()方法。

所以权限组的产生还在PackageManagerService里面,下面去PackageManagerService.java文件去看下:

public class PackageManagerService extends IPackageManager.Stub {
    ***
    @Override
    public @NonNull ParceledListSlice getAllPermissionGroups(int flags) {
        // reader
        synchronized (mPackages) {
        
            // 看来权限组的数据源,要去追踪下mPermissionGroups变量的构造
            final int N = mPermissionGroups.size();
            ArrayList out
                    = new ArrayList(N);
            for (PackageParser.PermissionGroup pg : mPermissionGroups.values()) {
                out.add(PackageParser.generatePermissionGroupInfo(pg, flags));
            }
            return new ParceledListSlice<>(out);
        }
    }

    ***
    private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
            final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
            throws PackageManagerException {
            ****
                        for (i=0; i

系统启动PackageManagerService的时候,扫描安装应用的时候,扫描到framework-res.apk文件的时候,解析里面的AndroidManifest.xml文件

    
    
        
    
    
        
            
    
        
    
    
        
    
    
        
    
    
        
    
    
        
    
    
        
    
    
        
    
    
        

2. xml对应的数据结构mPermissionGroups

点击权限组弹出的界面:

PermissionAppsFragments.java文件

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setLoading(true /* loading */, false /* animate */);
        ****
        String groupName = getArguments().getString(Intent.EXTRA_PERMISSION_NAME);
        mPermissionApps = new PermissionApps(getActivity(), groupName, this);
        mPermissionApps.refresh(true);
    }

PermissionApps根据传入的groupName,来返回所有的申请该权限组内权限的应用

    @Override
    public void onPermissionsLoaded(PermissionApps permissionApps) {
            for (PermissionApp app : permissionApps.getApps()) {
            ......
            }

遍历过滤app,然后显示出来

关闭or授予权限执行的操作

//PackageInstaller/src/com/android/packageinstaller/permission/ui/handheld/PermissionAppsFragment.java

public final class PermissionAppsFragment extends PermissionsFrameFragment implements Callback,
        Preference.OnPreferenceChangeListener {

    @Override
    public boolean onPreferenceChange(final Preference preference, Object newValue) {
        String pkg = preference.getKey();
        final PermissionApp app = mPermissionApps.getApp(pkg);

        if (app == null) {
            return false;
        }

        addToggledGroup(app.getPackageName(), app.getPermissionGroup());

        if (LocationUtils.isLocationGroupAndProvider(mPermissionApps.getGroupName(),
                app.getPackageName())) {
            LocationUtils.showLocationDialog(getContext(), app.getLabel());
            return false;
        }
        if (newValue == Boolean.TRUE) {
            app.grantRuntimePermissions();
        } else {
            final boolean grantedByDefault = app.hasGrantedByDefaultPermissions();
            if (grantedByDefault || (!app.hasRuntimePermissions() && !mHasConfirmedRevoke)) {
                new AlertDialog.Builder(getContext())
                        .setMessage(grantedByDefault ? R.string.system_warning
                                : R.string.old_sdk_deny_warning)
                        .setNegativeButton(R.string.cancel, null)
                        .setPositiveButton(R.string.grant_dialog_button_deny_anyway,
                                new OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                ((SwitchPreference) preference).setChecked(false);
                                app.revokeRuntimePermissions();
                                if (!grantedByDefault) {
                                    mHasConfirmedRevoke = true;
                                }
                            }
                        })
                        .show();
                return false;
            } else {
                app.revokeRuntimePermissions();
            }
        }
        return true;
    }

其中app.grantRuntimePermissionsapp.revokeRuntimePermissions();是真正授予或关闭权限的操作

app.grantRuntimePermissions 和 app.revokeRuntimePermissions()

file:  PermissionsState.java
    /**
     * Grant a runtime permission for a given device user.
     *
     * @param permission The permission to grant.
     * @param userId The device user id.
     * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
     *     or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
     *     #PERMISSION_OPERATION_FAILURE}.
     */
    public int grantRuntimePermission(BasePermission permission, int userId) {
        enforceValidUserId(userId);
        if (userId == UserHandle.USER_ALL) {
            return PERMISSION_OPERATION_FAILURE;
        }
        
        //转而去调用 grantPermission()方法
        return grantPermission(permission, userId);
    }
    

    private int grantPermission(BasePermission permission, int userId) {
        if (hasPermission(permission.name, userId)) {
            return PERMISSION_OPERATION_FAILURE;
        }

        final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
        final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;

        //将permission对象封装成 PermissionData(PermissionData是个内部类)
        PermissionData permissionData = ensurePermissionData(permission);
        
        //构建PermissionState(首次),将PermissionState的mGranted属性置true
        if (!permissionData.grant(userId)) {
            return PERMISSION_OPERATION_FAILURE;
        }

        if (hasGids) {
            final int[] newGids = computeGids(userId);
            if (oldGids.length != newGids.length) {
                return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
            }
        }

        return PERMISSION_OPERATION_SUCCESS;
    }
    //file: PermissionsState.java
    private static final class PermissionData {
    ....
    
        // grant方法,将该PermissionState对象的mGranted属性置true
        public boolean grant(int userId) {
            if (!isCompatibleUserId(userId)) {
                return false;
            }

            if (isGranted(userId)) {
                return false;
            }

            PermissionState userState = mUserStates.get(userId);
            if (userState == null) {
                userState = new PermissionState(mPerm.name);
                mUserStates.put(userId, userState);
            }

            userState.mGranted = true;

            return true;
        }

动态权限写的文件

    file:    /data/system/users/0/runtime-permissions.xml   


  
    
    
  
  
    
    
    
    
    
    
  
  
    
    
    
    
    
    
  
  
    
    
    
    
    
    
    
    
    
    
    
    
  

shared-user标签对应packageManagerService.java构造器中的

    file: PackageManagerService.java
    public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
            ......
        mSettings = new Settings(mPackages);
        //为系统中一些重要的应用提前授予运行时权限
        mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
        mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
                ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
           ......
           }
    file: /frameworks/base/services/core/java/com/android/server/pm/Settings.java
            private void writePermissionsSync(int userId) {
            AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId));

            ArrayMap> permissionsForPackage = new ArrayMap<>();
            ArrayMap> permissionsForSharedUser = new ArrayMap<>();

            synchronized (mLock) {
                mWriteScheduled.delete(userId);

                final int packageCount = mPackages.size();
                for (int i = 0; i < packageCount; i++) {
                    String packageName = mPackages.keyAt(i);
                    PackageSetting packageSetting = mPackages.valueAt(i);
                    if (packageSetting.sharedUser == null) {
                        PermissionsState permissionsState = packageSetting.getPermissionsState();
                        List permissionsStates = permissionsState
                                .getRuntimePermissionStates(userId);
                        if (!permissionsStates.isEmpty()) {
                            permissionsForPackage.put(packageName, permissionsStates);
                        }
                    }
                }

                final int sharedUserCount = mSharedUsers.size();
                for (int i = 0; i < sharedUserCount; i++) {
                    String sharedUserName = mSharedUsers.keyAt(i);
                    SharedUserSetting sharedUser = mSharedUsers.valueAt(i);
                    PermissionsState permissionsState = sharedUser.getPermissionsState();
                    List permissionsStates = permissionsState
                            .getRuntimePermissionStates(userId);
                    if (!permissionsStates.isEmpty()) {
                        permissionsForSharedUser.put(sharedUserName, permissionsStates);
                    }
                }
            }

            FileOutputStream out = null;
            try {
                out = destination.startWrite();

                XmlSerializer serializer = Xml.newSerializer();
                serializer.setOutput(out, StandardCharsets.UTF_8.name());
                serializer.setFeature(
                        "http://xmlpull.org/v1/doc/features.html#indent-output", true);
                serializer.startDocument(null, true);

                serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);

                String fingerprint = mFingerprints.get(userId);
                if (fingerprint != null) {
                    serializer.attribute(null, ATTR_FINGERPRINT, fingerprint);
                }

                final int packageCount = permissionsForPackage.size();
                for (int i = 0; i < packageCount; i++) {
                    String packageName = permissionsForPackage.keyAt(i);
                    List permissionStates = permissionsForPackage.valueAt(i);
                    serializer.startTag(null, TAG_PACKAGE);
                    serializer.attribute(null, ATTR_NAME, packageName);
                    writePermissions(serializer, permissionStates);
                    serializer.endTag(null, TAG_PACKAGE);
                }

                final int sharedUserCount = permissionsForSharedUser.size();
                for (int i = 0; i < sharedUserCount; i++) {
                    String packageName = permissionsForSharedUser.keyAt(i);
                    List permissionStates = permissionsForSharedUser.valueAt(i);
                    serializer.startTag(null, TAG_SHARED_USER);
                    serializer.attribute(null, ATTR_NAME, packageName);
                    writePermissions(serializer, permissionStates);
                    serializer.endTag(null, TAG_SHARED_USER);
                }

                serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);

                // Now any restored permission grants that are waiting for the apps
                // in question to be installed.  These are stored as per-package
                // TAG_RESTORED_RUNTIME_PERMISSIONS blocks, each containing some
                // number of individual permission grant entities.
                if (mRestoredUserGrants.get(userId) != null) {
                    ArrayMap> restoredGrants =
                            mRestoredUserGrants.get(userId);
                    if (restoredGrants != null) {
                        final int pkgCount = restoredGrants.size();
                        for (int i = 0; i < pkgCount; i++) {
                            final ArraySet pkgGrants =
                                    restoredGrants.valueAt(i);
                            if (pkgGrants != null && pkgGrants.size() > 0) {
                                final String pkgName = restoredGrants.keyAt(i);
                                serializer.startTag(null, TAG_RESTORED_RUNTIME_PERMISSIONS);
                                serializer.attribute(null, ATTR_PACKAGE_NAME, pkgName);

                                final int N = pkgGrants.size();
                                for (int z = 0; z < N; z++) {
                                    RestoredPermissionGrant g = pkgGrants.valueAt(z);
                                    serializer.startTag(null, TAG_PERMISSION_ENTRY);
                                    serializer.attribute(null, ATTR_NAME, g.permissionName);

                                    if (g.granted) {
                                        serializer.attribute(null, ATTR_GRANTED, "true");
                                    }

                                    if ((g.grantBits&FLAG_PERMISSION_USER_SET) != 0) {
                                        serializer.attribute(null, ATTR_USER_SET, "true");
                                    }
                                    if ((g.grantBits&FLAG_PERMISSION_USER_FIXED) != 0) {
                                        serializer.attribute(null, ATTR_USER_FIXED, "true");
                                    }
                                    if ((g.grantBits&FLAG_PERMISSION_REVOKE_ON_UPGRADE) != 0) {
                                        serializer.attribute(null, ATTR_REVOKE_ON_UPGRADE, "true");
                                    }
                                    serializer.endTag(null, TAG_PERMISSION_ENTRY);
                                }
                                serializer.endTag(null, TAG_RESTORED_RUNTIME_PERMISSIONS);
                            }
                        }
                    }
                }

                serializer.endDocument();
                destination.finishWrite(out);

                if (Build.FINGERPRINT.equals(fingerprint)) {
                    mDefaultPermissionsGranted.put(userId, true);
                }
            // Any error while writing is fatal.
            } catch (Throwable t) {
                Slog.wtf(PackageManagerService.TAG,
                        "Failed to write settings, restoring backup", t);
                destination.failWrite(out);
            } finally {
                IoUtils.closeQuietly(out);
            }
        }

二、默认应用

默认应用的一些配置信息都写在了package-restrictions.xml 文件

默认浏览器配置

系统记录默认浏览器的位置在:

手机上的文件位置:data/system/users/0/package-restrictions.xml
    
        
        
    

源码配置默认浏览器的地方:
frameworks/base/core/res/res/values/config.xml

    //file: frameworks/base/core/res/res/values/config.xml
        
    

恢复默认浏览器的代码:


    //file: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
    private void applyFactoryDefaultBrowserLPw(int userId) {
        // The default browser app's package name is stored in a string resource,
        // with a product-specific overlay used for vendor customization.
        String browserPkg = mContext.getResources().getString(
                com.android.internal.R.string.default_browser);
        if (!TextUtils.isEmpty(browserPkg)) {
            // non-empty string => required to be a known package
            PackageSetting ps = mSettings.mPackages.get(browserPkg);
            if (ps == null) {
                Slog.e(TAG, "Product default browser app does not exist: " + browserPkg);
                browserPkg = null;
            } else {
                mSettings.setDefaultBrowserPackageNameLPw(browserPkg, userId);
            }
        }

        // Nothing valid explicitly set? Make the factory-installed browser the explicit
        // default.  If there's more than one, just leave everything alone.
        if (browserPkg == null) {
            calculateDefaultBrowserLPw(userId);
        }
    }

    private void calculateDefaultBrowserLPw(int userId) {
        List allBrowsers = resolveAllBrowserApps(userId);
        final String browserPkg = (allBrowsers.size() == 1) ? allBrowsers.get(0) : null;
        mSettings.setDefaultBrowserPackageNameLPw(browserPkg, userId);
    }

从代码可以看出,系统会去读取frameworks/base/core/res/res/values/config.xml文件中的default_browser参数,来获取默认浏览器的包名,然后查询Settings中有没有该包的信息(没有的话输出错误log),然后调用Settings.setDefaultBrowserPackageNameLPw()方法,最终写入package-restrictions.xml文件的标签。

    //file: framework/base/services/core/java/com/android/server/pm/Settings.java
    boolean setDefaultBrowserPackageNameLPw(String packageName, int userId) {
        if (userId == UserHandle.USER_ALL) {
            return false;
        }
        mDefaultBrowserApp.put(userId, packageName);
        writePackageRestrictionsLPr(userId);
        return true;
    }

其中 writePackageRestrictionsLPr(userId)就是写package-restrictions.xml文件的操作,其中就 包含默认浏览器的标签

  //file: framework/base/services/core/java/com/android/server/pm/Settings.java
    void writePackageRestrictionsLPr(int userId) {
        if (DEBUG_MU) {
            Log.i(TAG, "Writing package restrictions for user=" + userId);
        }
        // Keep the old stopped packages around until we know the new ones have
        // been successfully written.
        File userPackagesStateFile = getUserPackagesStateFile(userId);
        File backupFile = getUserPackagesStateBackupFile(userId);
        new File(userPackagesStateFile.getParent()).mkdirs();
        if (userPackagesStateFile.exists()) {
            // Presence of backup settings file indicates that we failed
            // to persist packages earlier. So preserve the older
            // backup for future reference since the current packages
            // might have been corrupted.
            if (!backupFile.exists()) {
                if (!userPackagesStateFile.renameTo(backupFile)) {
                    Slog.wtf(PackageManagerService.TAG,
                            "Unable to backup user packages state file, "
                            + "current changes will be lost at reboot");
                    return;
                }
            } else {
                userPackagesStateFile.delete();
                Slog.w(PackageManagerService.TAG, "Preserving older stopped packages backup");
            }
        }

        try {
            final FileOutputStream fstr = new FileOutputStream(userPackagesStateFile);
            final BufferedOutputStream str = new BufferedOutputStream(fstr);

            final XmlSerializer serializer = new FastXmlSerializer();
            serializer.setOutput(str, StandardCharsets.UTF_8.name());
            serializer.startDocument(null, true);
            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);

            serializer.startTag(null, TAG_PACKAGE_RESTRICTIONS);

            for (final PackageSetting pkg : mPackages.values()) {
                final PackageUserState ustate = pkg.readUserState(userId);
                if (DEBUG_MU) Log.i(TAG, "  pkg=" + pkg.name + ", state=" + ustate.enabled);

                serializer.startTag(null, TAG_PACKAGE);
                serializer.attribute(null, ATTR_NAME, pkg.name);
                if (ustate.ceDataInode != 0) {
                    XmlUtils.writeLongAttribute(serializer, ATTR_CE_DATA_INODE, ustate.ceDataInode);
                }
                if (!ustate.installed) {
                    serializer.attribute(null, ATTR_INSTALLED, "false");
                }
                if (ustate.stopped) {
                    serializer.attribute(null, ATTR_STOPPED, "true");
                }
                if (ustate.notLaunched) {
                    serializer.attribute(null, ATTR_NOT_LAUNCHED, "true");
                }
                if (ustate.hidden) {
                    serializer.attribute(null, ATTR_HIDDEN, "true");
                }
                if (ustate.suspended) {
                    serializer.attribute(null, ATTR_SUSPENDED, "true");
                }
                if (ustate.blockUninstall) {
                    serializer.attribute(null, ATTR_BLOCK_UNINSTALL, "true");
                }
                if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) {
                    serializer.attribute(null, ATTR_ENABLED,
                            Integer.toString(ustate.enabled));
                    if (ustate.lastDisableAppCaller != null) {
                        serializer.attribute(null, ATTR_ENABLED_CALLER,
                                ustate.lastDisableAppCaller);
                    }
                }
                if (ustate.domainVerificationStatus !=
                        PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED) {
                    XmlUtils.writeIntAttribute(serializer, ATTR_DOMAIN_VERIFICATON_STATE,
                            ustate.domainVerificationStatus);
                }
                if (ustate.appLinkGeneration != 0) {
                    XmlUtils.writeIntAttribute(serializer, ATTR_APP_LINK_GENERATION,
                            ustate.appLinkGeneration);
                }
                if (!ArrayUtils.isEmpty(ustate.enabledComponents)) {
                    serializer.startTag(null, TAG_ENABLED_COMPONENTS);
                    for (final String name : ustate.enabledComponents) {
                        serializer.startTag(null, TAG_ITEM);
                        serializer.attribute(null, ATTR_NAME, name);
                        serializer.endTag(null, TAG_ITEM);
                    }
                    serializer.endTag(null, TAG_ENABLED_COMPONENTS);
                }
                if (!ArrayUtils.isEmpty(ustate.disabledComponents)) {
                    serializer.startTag(null, TAG_DISABLED_COMPONENTS);
                    for (final String name : ustate.disabledComponents) {
                        serializer.startTag(null, TAG_ITEM);
                        serializer.attribute(null, ATTR_NAME, name);
                        serializer.endTag(null, TAG_ITEM);
                    }
                    serializer.endTag(null, TAG_DISABLED_COMPONENTS);
                }

                serializer.endTag(null, TAG_PACKAGE);
            }

            writePreferredActivitiesLPr(serializer, userId, true);
            writePersistentPreferredActivitiesLPr(serializer, userId);
            writeCrossProfileIntentFiltersLPr(serializer, userId);
            //写默认浏览器操作
            writeDefaultAppsLPr(serializer, userId);

            serializer.endTag(null, TAG_PACKAGE_RESTRICTIONS);

            serializer.endDocument();

            str.flush();
            FileUtils.sync(fstr);
            str.close();

            // New settings successfully written, old ones are no longer
            // needed.
            backupFile.delete();
            FileUtils.setPermissions(userPackagesStateFile.toString(),
                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
                    -1, -1);

            // Done, all is good!
            return;
        } catch(java.io.IOException e) {
            Slog.wtf(PackageManagerService.TAG,
                    "Unable to write package manager user packages state, "
                    + " current changes will be lost at reboot", e);
        }

        // Clean up partially written files
        if (userPackagesStateFile.exists()) {
            if (!userPackagesStateFile.delete()) {
                Log.i(PackageManagerService.TAG, "Failed to clean up mangled file: "
                        + mStoppedPackagesFilename);
            }
        }
    }
    

如下便是写默认浏览器和默认拨号应用的操作,各自对应的标签是

    //file: framework/base/services/core/java/com/android/server/pm/Settings.java
    void writeDefaultAppsLPr(XmlSerializer serializer, int userId)
            throws IllegalArgumentException, IllegalStateException, IOException {
        serializer.startTag(null, TAG_DEFAULT_APPS);
        String defaultBrowser = mDefaultBrowserApp.get(userId);
        if (!TextUtils.isEmpty(defaultBrowser)) {
        
            //其中 TAG_DEFAULT_BROWSER = "default-browser"
            serializer.startTag(null, TAG_DEFAULT_BROWSER);
            serializer.attribute(null, ATTR_PACKAGE_NAME, defaultBrowser);
            serializer.endTag(null, TAG_DEFAULT_BROWSER);
        }
        String defaultDialer = mDefaultDialerApp.get(userId);
        if (!TextUtils.isEmpty(defaultDialer)) {
        
            //其中TAG_DEFAULT_DIALER = "default-dialer"
            serializer.startTag(null, TAG_DEFAULT_DIALER);
            serializer.attribute(null, ATTR_PACKAGE_NAME, defaultDialer);
            serializer.endTag(null, TAG_DEFAULT_DIALER);
        }
        serializer.endTag(null, TAG_DEFAULT_APPS);
    }

你可能感兴趣的:(2018-04-09应用管理)