前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 8.0(Android O) AccountManager 行为变更

Android 8.0(Android O) AccountManager 行为变更

作者头像
程序员徐公
发布2018-09-18 16:51:29
1.3K0
发布2018-09-18 16:51:29
举报

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://cloud.tencent.com/developer/article/1341871

前言

在公司的项目中,使用 AccountManager 统一管理//获取帐号列表/或对应帐号类型的某个帐号

AccountManager#getAccounts()/getAccountsByType(String accountType) 获取不到 AccountManager 里面的信息。

第一时间的反应是去查询官方 Android O 的行为变更,果然,Android O 对 AccountManager 做出了相应的修改。

官方文档地址

Account access and discoverability In Android 8.0 (API level 26), apps can no longer get access to user accounts unless the authenticator owns the accounts or the user grants that access. The GET_ACCOUNTS permission is no longer sufficient. To be granted access to an account, apps should either use AccountManager.newChooseAccountIntent() or an authenticator-specific method. After getting access to accounts, an app can can call AccountManager.getAccounts() to access them. Android 8.0 deprecates LOGIN_ACCOUNTS_CHANGED_ACTION. Apps should instead use addOnAccountsUpdatedListener() to get updates about accounts during runtime. For information about new APIs and methods added for account access and discoverability, see Account Access and Discoverability in the New APIs section of this document.

简单来说,如果你的应用

在 Android 8.0 中,如果你的 target sdk 大于 25,通过 AccountManager.getAccounts() 无法获取 Account 的相关信息,即使你的 App 拥有 GET_ACCOUNTS permission 权限。

如果想适配 Android 8.0,你可以通过 newChooseAccountIntent 来申请权限,接着通过 AccountManager.getAccounts() 即可以正常获取权限。


Android O AccountManager 适配方案

以下的解决方案来自

android 8.0 —AccountManager之行为变更

  • Target API level below O and have deprecated GET_ACCOUNTS permission.
  • Have GET_ACCOUNTS_PRIVILEGED permission.
  • Have the same signature as authenticator.
  • Have READ_CONTACTS permission and account type may be associated with contacts data - (verified by WRITE_CONTACTS permission check for the authenticator).

第一种情况:

targetSdkVersion<26,判断逻辑和8.0之前的判断逻辑是一样的,会检查 Manifest.permission.GET_ACCOUNTS 的权限(android6.0及以上是运行时权限,需动态申请)

第二种情况:

有权限 Manifest.permission.GET_ACCOUNTS_PRIVILEGED,只有priv/app目录下的app声明之后才会授予此权限 (不管targetSdkVersion<26,还是>=26,有此权限,都有getAccountsXXX的权限 )

第三种情况:

和注册此帐号类型的authenticator app签名一致(同第二种情况,与targetSdkVersion无关,只要签名一致,即可在8.0的机器上有权限调用getAccountsXXX)

第四种情况:

caller app有权限Manifest.permission.READ_CONTACTS,该accountType的authenticator app要有Manifest.permission.WRITE_CONTACTS(这两个都是dangerous permission,需要动态申请)

根据Requesting Permissions才发现,read contacts,write contacts和get account这三个权限是属于同一个权限组的

官方推荐适配方案

如果上面四个条件你都不满足,AccountManager还提供里另外两个接口:

/**
* 返回用户选择授予获取帐号的弹窗Intent
*/
static public Intent newChooseAccountIntent(Account selectedAccount,
ArrayList<Account> allowableAccounts, 
String[] allowableAccountTypes, 
String descriptionOverrideText, 
String addAccountAuthTokenType, 
String[] addAccountRequiredFeatures,
Bundle addAccountOptions)
/**
* 将某个帐号对特定包名可见性(允许/拒绝)
* 只有和account的authenticator app签名一致才能调用此接口
*/
public boolean setAccountVisibility(Account account, String packageName, @AccountVisibility int visibility)
/**
* 此外,android8.0还追加下面接口,与setAccountVisibility接口相同
* 在登录成功,向AccountManager数据库中添加帐号时添加对特定包名的可见性
* 名义上,只有authenticator app才可以调用此接口
*/
public boolean addAccountExplicitly(Account account, String password, Bundle extras, Map<String, Integer> visibility)

上述接口要么是用户来选择授权同意,要么是authenticator app给予授权,具体来说android 8.0更加加强了用户的隐私数据安全性

newChooseAccountIntent显示给用户的弹窗样式如下:

从源码的角度分析这几种解决方案

上面说了几种解决方案,为什么这几种方案会有效果呢?

下面让我们一起从源码的角度来解读。

@NonNull
public Account[] getAccountsByType(String type) {
    return getAccountsByTypeAsUser(type, Process.myUserHandle());
}

/** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */
@NonNull
public Account[] getAccountsByTypeAsUser(String type, UserHandle userHandle) {
    try {
        return mService.getAccountsAsUser(type, userHandle.getIdentifier(),
                mContext.getOpPackageName());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

getAccountsByType 方法里面调用 getAccountsByTypeAsUser 方法,而在 getAccountsByTypeAsUser 方法里面,有调用 mService 的 getAccountsAsUser 方法。

那这个 mService 是什么东东呢?

private final IAccountManager mService;

可以看到其实是一个 AIDl,里面有若干方法

interface IAccountManager {

    Account[] getAccounts(String accountType, String opPackageName);
    Account[] getAccountsForPackage(String packageName, int uid, String opPackageName);
    Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName);
    Account[] getAccountsAsUser(String accountType, int userId, String opPackageName);
    void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features,
        String opPackageName);
    void getAccountByTypeAndFeatures(in IAccountManagerResponse response, String accountType,
        in String[] features, String opPackageName);
    void getAccountsByFeatures(in IAccountManagerResponse response, String accountType,
        in String[] features, String opPackageName);

   // 省略了若干方法

}

而这个 AIDL 最终会调用到 AccountManagerService 的相应方法

public class AccountManagerService
        extends IAccountManager.Stub
        implements RegisteredServicesCacheListener<AuthenticatorDescription>

接下来,我们一起来看一下 AccountManagerService 的 getAccountsAsUser 方法。 getAccountsAsUser 方法里面很简单,只是调用了 getAccountsAsUserForPackage 方法去获取结果。

@Override
@NonNull
public Account[] getAccountsAsUser(String type, int userId, String opPackageName) {
    int callingUid = Binder.getCallingUid();
    mAppOpsManager.checkPackage(callingUid, opPackageName);
    return getAccountsAsUserForPackage(type, userId, opPackageName /* callingPackage */, -1,
            opPackageName, false /* includeUserManagedNotVisible */);
}

@NonNull
private Account[] getAccountsAsUserForPackage(
        String type,
        int userId,
        String callingPackage,
        int packageUid,
        String opPackageName,
        boolean includeUserManagedNotVisible) {


  ---- // 省略若干方法

    long identityToken = clearCallingIdentity();
    try {
        UserAccounts accounts = getUserAccounts(userId);
        return getAccountsInternal(
                accounts,
                callingUid,
                opPackageName,
                visibleAccountTypes,
                includeUserManagedNotVisible);
    } finally {
        restoreCallingIdentity(identityToken);
    }
}

在 getAccountsAsUserForPackage 方法里面,经过一系列的判断,最终又会调用到 getAccountsInternal 方法。

@NonNull
private Account[] getAccountsInternal(
        UserAccounts userAccounts,
        int callingUid,
        String callingPackage,
        List<String> visibleAccountTypes,
        boolean includeUserManagedNotVisible) {
    ArrayList<Account> visibleAccounts = new ArrayList<>();
    for (String visibleType : visibleAccountTypes) {
        Account[] accountsForType = getAccountsFromCache(
                userAccounts, visibleType, callingUid, callingPackage,
                includeUserManagedNotVisible);
        if (accountsForType != null) {
            visibleAccounts.addAll(Arrays.asList(accountsForType));
        }
    }
    Account[] result = new Account[visibleAccounts.size()];
    for (int i = 0; i < visibleAccounts.size(); i++) {
        result[i] = visibleAccounts.get(i);
    }
    return result;
}

在 getAccountsInternal 方法里面,又会调用 getAccountsFromCache 去获取结果

protected Account[] getAccountsFromCache(UserAccounts userAccounts, String accountType,
        int callingUid, @Nullable String callingPackage, boolean includeManagedNotVisible) {
    Preconditions.checkState(!Thread.holdsLock(userAccounts.cacheLock),
            "Method should not be called with cacheLock");
    if (accountType != null) {
        Account[] accounts;
        synchronized (userAccounts.cacheLock) {
            accounts = userAccounts.accountCache.get(accountType);
        }
        if (accounts == null) {
            return EMPTY_ACCOUNT_ARRAY;
        } else {
            return filterAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
                    callingUid, callingPackage, includeManagedNotVisible);
        }
    } else {
        int totalLength = 0;
        Account[] accountsArray;
        synchronized (userAccounts.cacheLock) {
            for (Account[] accounts : userAccounts.accountCache.values()) {
                totalLength += accounts.length;
            }
            if (totalLength == 0) {
                return EMPTY_ACCOUNT_ARRAY;
            }
            accountsArray = new Account[totalLength];
            totalLength = 0;
            for (Account[] accountsOfType : userAccounts.accountCache.values()) {
                System.arraycopy(accountsOfType, 0, accountsArray, totalLength,
                        accountsOfType.length);
                totalLength += accountsOfType.length;
            }
        }
        return filterAccounts(userAccounts, accountsArray, callingUid, callingPackage,
                includeManagedNotVisible);
    }
}

而在 getAccountsFromCache 里面,不管 accountType 是否为空,最终都会调用到 filterAccounts 方法。

@NonNull
private Account[] filterAccounts(UserAccounts accounts, Account[] unfiltered, int callingUid,
        @Nullable String callingPackage, boolean includeManagedNotVisible) {
    String visibilityFilterPackage = callingPackage;
    if (visibilityFilterPackage == null) {
        visibilityFilterPackage = getPackageNameForUid(callingUid);
    }
    Map<Account, Integer> firstPass = new LinkedHashMap<>();
    for (Account account : unfiltered) {
        int visibility = resolveAccountVisibility(account, visibilityFilterPackage, accounts);
        if ((visibility == AccountManager.VISIBILITY_VISIBLE
                || visibility == AccountManager.VISIBILITY_USER_MANAGED_VISIBLE)
                || (includeManagedNotVisible
                        && (visibility
                                == AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE))) {
            firstPass.put(account, visibility);
        }
    }
    Map<Account, Integer> secondPass =
            filterSharedAccounts(accounts, firstPass, callingUid, callingPackage);

    Account[] filtered = new Account[secondPass.size()];
    filtered = secondPass.keySet().toArray(filtered);
    return filtered;
}

在 filterAccounts 里面,又会调用 resolveAccountVisibility 去判断我们的 CallerApp 是否可以访问我们的 Account。

private Integer resolveAccountVisibility(Account account, @NonNull String packageName,
        UserAccounts accounts) {
    Preconditions.checkNotNull(packageName, "packageName cannot be null");
    int uid = -1;
    try {
        long identityToken = clearCallingIdentity();
        try {
            uid = mPackageManager.getPackageUidAsUser(packageName, accounts.userId);
        } finally {
            restoreCallingIdentity(identityToken);
        }
    } catch (NameNotFoundException e) {
        Log.d(TAG, "Package not found " + e.getMessage());
        return AccountManager.VISIBILITY_NOT_VISIBLE;
    }

    // System visibility can not be restricted.
    if (UserHandle.isSameApp(uid, Process.SYSTEM_UID)) {
        return AccountManager.VISIBILITY_VISIBLE;
    }

    int signatureCheckResult =
            checkPackageSignature(account.type, uid, accounts.userId);

    // Authenticator can not restrict visibility to itself.
    if (signatureCheckResult == SIGNATURE_CHECK_UID_MATCH) {
        return AccountManager.VISIBILITY_VISIBLE; // Authenticator can always see the account
    }

    // Return stored value if it was set.
    int visibility = getAccountVisibilityFromCache(account, packageName, accounts);

    if (AccountManager.VISIBILITY_UNDEFINED != visibility) {
        return visibility;
    }

    boolean isPrivileged = isPermittedForPackage(packageName, uid, accounts.userId,
            Manifest.permission.GET_ACCOUNTS_PRIVILEGED);

    // Device/Profile owner gets visibility by default.
    if (isProfileOwner(uid)) {
        return AccountManager.VISIBILITY_VISIBLE;
    }

     // target  sdk < 26
    boolean preO = isPreOApplication(packageName);
    // 签名是否一致
    // target  sdk < 26  小于 26 ,并且拥有 Manifest.permission.GET_ACCOUNTS 权限 
    // CallerApp 拥有  Manifest.permission.READ_CONTACTS ,authenticator APP 拥有 Manifest.permission.WRITE_CONTACTS
    // 是否拥有 Manifest.permission.GET_ACCOUNTS_PRIVILEGED 权限
    if ((signatureCheckResult != SIGNATURE_CHECK_MISMATCH)
            || (preO && checkGetAccountsPermission(packageName, uid, accounts.userId))
            || (checkReadContactsPermission(packageName, uid, accounts.userId)
                && accountTypeManagesContacts(account.type, accounts.userId))
            || isPrivileged) {
        // Use legacy for preO apps with GET_ACCOUNTS permission or pre/postO with signature
        // match.
        visibility = getAccountVisibilityFromCache(account,
                AccountManager.PACKAGE_NAME_KEY_LEGACY_VISIBLE, accounts);
        if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
            visibility = AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
        }
    } else {
        visibility = getAccountVisibilityFromCache(account,
                AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, accounts);
        if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
            visibility = AccountManager.VISIBILITY_USER_MANAGED_NOT_VISIBLE;
        }
    }
    return visibility;
}

我们主要看这一段就好,从这一段我们可以看到满足下列其中一个条件,即可获取得到相关的 Account 信息。

  • 签名一致
  • target sdk < 26 小于 26 ,并且拥有 Manifest.permission.GET_ACCOUNTS 权限
  • CallerApp 拥有 Manifest.permission.READ_CONTACTS ,authenticator APP 拥有 Manifest.permission.WRITE_CONTACTS
  • 拥有 Manifest.permission.GET_ACCOUNTS_PRIVILEGED 权限
// target  sdk < 26
    boolean preO = isPreOApplication(packageName);
    // 签名是否一致
    // target  sdk < 26  小于 26 ,并且拥有 Manifest.permission.GET_ACCOUNTS 权限 
    // CallerApp 拥有  Manifest.permission.READ_CONTACTS ,authenticator APP 拥有 Manifest.permission.WRITE_CONTACTS
    // 是否拥有 Manifest.permission.GET_ACCOUNTS_PRIVILEGED 权限
    if ((signatureCheckResult != SIGNATURE_CHECK_MISMATCH)
            || (preO && checkGetAccountsPermission(packageName, uid, accounts.userId))
            || (checkReadContactsPermission(packageName, uid, accounts.userId)
                && accountTypeManagesContacts(account.type, accounts.userId))
            || isPrivileged) {
        // Use legacy for preO apps with GET_ACCOUNTS permission or pre/postO with signature
        // match.
        visibility = getAccountVisibilityFromCache(account,
                AccountManager.PACKAGE_NAME_KEY_LEGACY_VISIBLE, accounts);
        if (AccountManager.VISIBILITY_UNDEFINED == visibility) {
            visibility = AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
        }
    }

题外话

写这篇博客的主要目的其实不是为了讨论解决方案,而是为了记录一下跟踪 Framework 代码的一些历程吧。很多时候,我们知道了解决方案之后,往往没有往更深一层去了解,为什么这种解决方案是有效的。

或许刚开始跟踪源码的过程,是有点痛苦,也很想放弃,但如果当你坚持下来,找到解决方案的时候,你会莫名地发现有一种成就感。

参考博客:

android 8.0 —AccountManager之行为变更

转载请注明原博客地址

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018年04月26日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • Android O AccountManager 适配方案
    • 官方推荐适配方案
    • 从源码的角度分析这几种解决方案
    • 题外话
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档