add account manager permission checking
This commit is contained in:
143
api/current.xml
143
api/current.xml
@@ -122,6 +122,28 @@
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="ACCOUNT_MANAGER_SERVICE"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""android.permission.ACCOUNT_MANAGER_SERVICE""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="AUTHENTICATE_ACCOUNTS"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""android.permission.AUTHENTICATE_ACCOUNTS""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="BATTERY_STATS"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
@@ -573,6 +595,17 @@
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="MANAGE_ACCOUNTS"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""android.permission.MANAGE_ACCOUNTS""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="MANAGE_APP_TOKENS"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
@@ -1079,6 +1112,17 @@
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="USE_CREDENTIALS"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""android.permission.USE_CREDENTIALS""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="VIBRATE"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
@@ -15929,6 +15973,8 @@
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="context" type="android.content.Context">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<method name="addAccount"
|
||||
return="android.os.Bundle"
|
||||
@@ -16023,6 +16069,19 @@
|
||||
<exception name="NetworkErrorException" type="android.accounts.NetworkErrorException">
|
||||
</exception>
|
||||
</method>
|
||||
<method name="getAuthTokenLabel"
|
||||
return="java.lang.String"
|
||||
abstract="true"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="authTokenType" type="java.lang.String">
|
||||
</parameter>
|
||||
</method>
|
||||
<method name="getIAccountAuthenticator"
|
||||
return="android.accounts.IAccountAuthenticator"
|
||||
abstract="false"
|
||||
@@ -17335,6 +17394,17 @@
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="AUTH_TOKEN_LABEL_KEY"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
value=""authTokenLabelKey""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="BOOLEAN_RESULT_KEY"
|
||||
type="java.lang.String"
|
||||
transient="false"
|
||||
@@ -17715,6 +17785,23 @@
|
||||
<exception name="RemoteException" type="android.os.RemoteException">
|
||||
</exception>
|
||||
</method>
|
||||
<method name="getAuthTokenLabel"
|
||||
return="void"
|
||||
abstract="true"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="response" type="android.accounts.IAccountAuthenticatorResponse">
|
||||
</parameter>
|
||||
<parameter name="authTokenType" type="java.lang.String">
|
||||
</parameter>
|
||||
<exception name="RemoteException" type="android.os.RemoteException">
|
||||
</exception>
|
||||
</method>
|
||||
<method name="hasFeatures"
|
||||
return="void"
|
||||
abstract="true"
|
||||
@@ -138591,6 +138678,62 @@
|
||||
>
|
||||
</method>
|
||||
</class>
|
||||
<class name="Pair"
|
||||
extends="java.lang.Object"
|
||||
abstract="false"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<constructor name="Pair"
|
||||
type="android.util.Pair"
|
||||
static="false"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="first" type="F">
|
||||
</parameter>
|
||||
<parameter name="second" type="S">
|
||||
</parameter>
|
||||
</constructor>
|
||||
<method name="create"
|
||||
return="android.util.Pair<A, B>"
|
||||
abstract="false"
|
||||
native="false"
|
||||
synchronized="false"
|
||||
static="true"
|
||||
final="false"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
<parameter name="a" type="A">
|
||||
</parameter>
|
||||
<parameter name="b" type="B">
|
||||
</parameter>
|
||||
</method>
|
||||
<field name="first"
|
||||
type="F"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
<field name="second"
|
||||
type="S"
|
||||
transient="false"
|
||||
volatile="false"
|
||||
static="false"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
</class>
|
||||
<class name="PrintStreamPrinter"
|
||||
extends="java.lang.Object"
|
||||
abstract="false"
|
||||
|
||||
@@ -18,6 +18,11 @@ package android.accounts;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Binder;
|
||||
import android.util.Log;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.Context;
|
||||
import android.Manifest;
|
||||
|
||||
/**
|
||||
* Base class for creating AccountAuthenticators. This implements the IAccountAuthenticator
|
||||
@@ -25,10 +30,17 @@ import android.os.RemoteException;
|
||||
* AccountAuthenticators.
|
||||
*/
|
||||
public abstract class AbstractAccountAuthenticator {
|
||||
private final Context mContext;
|
||||
|
||||
public AbstractAccountAuthenticator(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
class Transport extends IAccountAuthenticator.Stub {
|
||||
public void addAccount(IAccountAuthenticatorResponse response, String accountType,
|
||||
String authTokenType, String[] requiredFeatures, Bundle options)
|
||||
throws RemoteException {
|
||||
checkBinderPermission();
|
||||
final Bundle result;
|
||||
try {
|
||||
result = AbstractAccountAuthenticator.this.addAccount(
|
||||
@@ -49,6 +61,7 @@ public abstract class AbstractAccountAuthenticator {
|
||||
|
||||
public void confirmPassword(IAccountAuthenticatorResponse response,
|
||||
Account account, String password) throws RemoteException {
|
||||
checkBinderPermission();
|
||||
boolean result;
|
||||
try {
|
||||
result = AbstractAccountAuthenticator.this.confirmPassword(
|
||||
@@ -69,6 +82,7 @@ public abstract class AbstractAccountAuthenticator {
|
||||
|
||||
public void confirmCredentials(IAccountAuthenticatorResponse response,
|
||||
Account account) throws RemoteException {
|
||||
checkBinderPermission();
|
||||
final Bundle result;
|
||||
try {
|
||||
result = AbstractAccountAuthenticator.this.confirmCredentials(
|
||||
@@ -83,9 +97,28 @@ public abstract class AbstractAccountAuthenticator {
|
||||
}
|
||||
}
|
||||
|
||||
public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
|
||||
String authTokenType)
|
||||
throws RemoteException {
|
||||
checkBinderPermission();
|
||||
try {
|
||||
Bundle result = new Bundle();
|
||||
result.putString(Constants.AUTH_TOKEN_LABEL_KEY,
|
||||
AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType));
|
||||
response.onResult(result);
|
||||
} catch (IllegalArgumentException e) {
|
||||
response.onError(Constants.ERROR_CODE_BAD_ARGUMENTS,
|
||||
"unknown authTokenType");
|
||||
} catch (UnsupportedOperationException e) {
|
||||
response.onError(Constants.ERROR_CODE_UNSUPPORTED_OPERATION,
|
||||
"getAuthTokenTypeLabel not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public void getAuthToken(IAccountAuthenticatorResponse response,
|
||||
Account account, String authTokenType, Bundle loginOptions)
|
||||
throws RemoteException {
|
||||
checkBinderPermission();
|
||||
try {
|
||||
final Bundle result = AbstractAccountAuthenticator.this.getAuthToken(
|
||||
new AccountAuthenticatorResponse(response), account,
|
||||
@@ -103,6 +136,7 @@ public abstract class AbstractAccountAuthenticator {
|
||||
|
||||
public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
|
||||
String authTokenType, Bundle loginOptions) throws RemoteException {
|
||||
checkBinderPermission();
|
||||
final Bundle result;
|
||||
try {
|
||||
result = AbstractAccountAuthenticator.this.updateCredentials(
|
||||
@@ -120,6 +154,7 @@ public abstract class AbstractAccountAuthenticator {
|
||||
|
||||
public void editProperties(IAccountAuthenticatorResponse response,
|
||||
String accountType) throws RemoteException {
|
||||
checkBinderPermission();
|
||||
final Bundle result;
|
||||
try {
|
||||
result = AbstractAccountAuthenticator.this.editProperties(
|
||||
@@ -136,6 +171,7 @@ public abstract class AbstractAccountAuthenticator {
|
||||
|
||||
public void hasFeatures(IAccountAuthenticatorResponse response,
|
||||
Account account, String[] features) throws RemoteException {
|
||||
checkBinderPermission();
|
||||
final Bundle result;
|
||||
try {
|
||||
result = AbstractAccountAuthenticator.this.hasFeatures(
|
||||
@@ -154,6 +190,14 @@ public abstract class AbstractAccountAuthenticator {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkBinderPermission() {
|
||||
final int uid = Binder.getCallingUid();
|
||||
final String perm = Manifest.permission.ACCOUNT_MANAGER_SERVICE;
|
||||
if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
|
||||
throw new SecurityException("caller uid " + uid + " lacks " + perm);
|
||||
}
|
||||
}
|
||||
|
||||
Transport mTransport = new Transport();
|
||||
|
||||
/**
|
||||
@@ -189,6 +233,7 @@ public abstract class AbstractAccountAuthenticator {
|
||||
public abstract Bundle getAuthToken(AccountAuthenticatorResponse response,
|
||||
Account account, String authTokenType, Bundle loginOptions)
|
||||
throws NetworkErrorException;
|
||||
public abstract String getAuthTokenLabel(String authTokenType);
|
||||
public abstract Bundle updateCredentials(AccountAuthenticatorResponse response,
|
||||
Account account, String authTokenType, Bundle loginOptions);
|
||||
public abstract Bundle hasFeatures(AccountAuthenticatorResponse response,
|
||||
|
||||
@@ -652,6 +652,13 @@ public class AccountManager {
|
||||
// that we see
|
||||
mActivity.startActivity(intent);
|
||||
// leave the Future running to wait for the real response to this request
|
||||
} else if (bundle.getBoolean("retry")) {
|
||||
try {
|
||||
doWork();
|
||||
} catch (RemoteException e) {
|
||||
// this will only happen if the system process is dead, which means
|
||||
// we will be dying ourselves
|
||||
}
|
||||
} else {
|
||||
set(bundle);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.RegisteredServicesCache;
|
||||
import android.database.Cursor;
|
||||
import android.database.DatabaseUtils;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
@@ -33,18 +35,25 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.os.SystemClock;
|
||||
import android.os.Binder;
|
||||
import android.os.SystemProperties;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Notification;
|
||||
import android.Manifest;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import com.android.internal.telephony.TelephonyIntents;
|
||||
import com.android.internal.R;
|
||||
@@ -63,7 +72,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
|
||||
private static final int TIMEOUT_DELAY_MS = 1000 * 60;
|
||||
private static final String DATABASE_NAME = "accounts.db";
|
||||
private static final int DATABASE_VERSION = 2;
|
||||
private static final int DATABASE_VERSION = 3;
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
@@ -92,6 +101,11 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
private static final String AUTHTOKENS_TYPE = "type";
|
||||
private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
|
||||
|
||||
private static final String TABLE_GRANTS = "grants";
|
||||
private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
|
||||
private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
|
||||
private static final String GRANTS_GRANTEE_UID = "uid";
|
||||
|
||||
private static final String TABLE_EXTRAS = "extras";
|
||||
private static final String EXTRAS_ID = "_id";
|
||||
private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
|
||||
@@ -107,8 +121,37 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
private static final Intent ACCOUNTS_CHANGED_INTENT =
|
||||
new Intent(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
|
||||
|
||||
private static final String COUNT_OF_MATCHING_GRANTS = ""
|
||||
+ "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
|
||||
+ " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
|
||||
+ " AND " + GRANTS_GRANTEE_UID + "=?"
|
||||
+ " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?"
|
||||
+ " AND " + ACCOUNTS_NAME + "=?"
|
||||
+ " AND " + ACCOUNTS_TYPE + "=?";
|
||||
|
||||
private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
|
||||
private static final int NOTIFICATION_ID = 234;
|
||||
private final AtomicInteger mNotificationIds = new AtomicInteger(1);
|
||||
|
||||
private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
|
||||
mCredentialsPermissionNotificationIds =
|
||||
new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
|
||||
private final HashMap<Account, Integer> mSigninRequiredNotificationIds =
|
||||
new HashMap<Account, Integer>();
|
||||
private static AtomicReference<AccountManagerService> sThis =
|
||||
new AtomicReference<AccountManagerService>();
|
||||
|
||||
private static final boolean isDebuggableMonkeyBuild =
|
||||
SystemProperties.getBoolean("ro.monkey", false)
|
||||
&& SystemProperties.getBoolean("ro.debuggable", false);
|
||||
/**
|
||||
* This should only be called by system code. One should only call this after the service
|
||||
* has started.
|
||||
* @return a reference to the AccountManagerService instance
|
||||
* @hide
|
||||
*/
|
||||
public static AccountManagerService getSingleton() {
|
||||
return sThis.get();
|
||||
}
|
||||
|
||||
public class AuthTokenKey {
|
||||
public final Account mAccount;
|
||||
@@ -163,9 +206,12 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
|
||||
|
||||
mSimWatcher = new SimWatcher(mContext);
|
||||
sThis.set(this);
|
||||
}
|
||||
|
||||
public String getPassword(Account account) {
|
||||
checkAuthenticateAccountsPermission(account);
|
||||
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
|
||||
@@ -186,6 +232,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public String getUserData(Account account, String key) {
|
||||
checkAuthenticateAccountsPermission(account);
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
|
||||
@@ -207,7 +254,6 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
cursor.close();
|
||||
}
|
||||
} finally {
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
} finally {
|
||||
@@ -235,6 +281,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public Account[] getAccounts() {
|
||||
checkReadAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
return getAccountsByType(null);
|
||||
@@ -244,6 +291,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public Account[] getAccountsByType(String accountType) {
|
||||
checkReadAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
|
||||
@@ -269,6 +317,8 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public boolean addAccount(Account account, String password, Bundle extras) {
|
||||
checkAuthenticateAccountsPermission(account);
|
||||
|
||||
// fails if the account already exists
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
@@ -318,6 +368,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public void removeAccount(Account account) {
|
||||
checkManageAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
@@ -330,6 +381,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public void invalidateAuthToken(String accountType, String authToken) {
|
||||
checkManageAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
@@ -404,12 +456,12 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
return getAuthToken(db, accountId, authTokenType);
|
||||
} finally {
|
||||
db.setTransactionSuccessful();
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
public String peekAuthToken(Account account, String authTokenType) {
|
||||
checkAuthenticateAccountsPermission(account);
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
return readAuthTokenFromDatabase(account, authTokenType);
|
||||
@@ -419,6 +471,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public void setAuthToken(Account account, String authTokenType, String authToken) {
|
||||
checkAuthenticateAccountsPermission(account);
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
cacheAuthToken(account, authTokenType, authToken);
|
||||
@@ -428,6 +481,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public void setPassword(Account account, String password) {
|
||||
checkAuthenticateAccountsPermission(account);
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
ContentValues values = new ContentValues();
|
||||
@@ -446,6 +500,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public void clearPassword(Account account) {
|
||||
checkManageAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
setPassword(account, null);
|
||||
@@ -455,6 +510,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public void setUserData(Account account, String key, String value) {
|
||||
checkAuthenticateAccountsPermission(account);
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
@@ -487,26 +543,39 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private void onResult(IAccountManagerResponse response, Bundle result) {
|
||||
try {
|
||||
response.onResult(result);
|
||||
} catch (RemoteException e) {
|
||||
// if the caller is dead then there is no one to care about remote
|
||||
// exceptions
|
||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||
Log.v(TAG, "failure while notifying response", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void getAuthToken(IAccountManagerResponse response, final Account account,
|
||||
final String authTokenType, final boolean notifyOnAuthFailure,
|
||||
final boolean expectActivityLaunch, final Bundle loginOptions) {
|
||||
checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
|
||||
final int callerUid = Binder.getCallingUid();
|
||||
final boolean permissionGranted = permissionIsGranted(account, authTokenType, callerUid);
|
||||
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
String authToken = readAuthTokenFromDatabase(account, authTokenType);
|
||||
if (authToken != null) {
|
||||
try {
|
||||
// if the caller has permission, do the peek. otherwise go the more expensive
|
||||
// route of starting a Session
|
||||
if (permissionGranted) {
|
||||
String authToken = readAuthTokenFromDatabase(account, authTokenType);
|
||||
if (authToken != null) {
|
||||
Bundle result = new Bundle();
|
||||
result.putString(Constants.AUTHTOKEN_KEY, authToken);
|
||||
result.putString(Constants.ACCOUNT_NAME_KEY, account.mName);
|
||||
result.putString(Constants.ACCOUNT_TYPE_KEY, account.mType);
|
||||
response.onResult(result);
|
||||
} catch (RemoteException e) {
|
||||
// if the caller is dead then there is no one to care about remote exceptions
|
||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||
Log.v(TAG, "failure while notifying response", e);
|
||||
}
|
||||
onResult(response, result);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
new Session(response, account.mType, expectActivityLaunch) {
|
||||
@@ -520,11 +589,27 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
|
||||
public void run() throws RemoteException {
|
||||
mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
|
||||
// If the caller doesn't have permission then create and return the
|
||||
// "grant permission" intent instead of the "getAuthToken" intent.
|
||||
if (!permissionGranted) {
|
||||
mAuthenticator.getAuthTokenLabel(this, authTokenType);
|
||||
} else {
|
||||
mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
|
||||
}
|
||||
}
|
||||
|
||||
public void onResult(Bundle result) {
|
||||
if (result != null) {
|
||||
if (result.containsKey(Constants.AUTH_TOKEN_LABEL_KEY)) {
|
||||
Intent intent = newGrantCredentialsPermissionIntent(account, callerUid,
|
||||
new AccountAuthenticatorResponse(this),
|
||||
authTokenType,
|
||||
result.getString(Constants.AUTH_TOKEN_LABEL_KEY));
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.INTENT_KEY, intent);
|
||||
onResult(bundle);
|
||||
return;
|
||||
}
|
||||
String authToken = result.getString(Constants.AUTHTOKEN_KEY);
|
||||
if (authToken != null) {
|
||||
String name = result.getString(Constants.ACCOUNT_NAME_KEY);
|
||||
@@ -539,7 +624,8 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
|
||||
Intent intent = result.getParcelable(Constants.INTENT_KEY);
|
||||
if (intent != null && notifyOnAuthFailure) {
|
||||
doNotification(result.getString(Constants.AUTH_FAILED_MESSAGE_KEY),
|
||||
doNotification(
|
||||
account, result.getString(Constants.AUTH_FAILED_MESSAGE_KEY),
|
||||
intent);
|
||||
}
|
||||
}
|
||||
@@ -551,10 +637,92 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private void createNoCredentialsPermissionNotification(Account account, Intent intent) {
|
||||
int uid = intent.getIntExtra(
|
||||
GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
|
||||
String authTokenType = intent.getStringExtra(
|
||||
GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE);
|
||||
String authTokenLabel = intent.getStringExtra(
|
||||
GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL);
|
||||
|
||||
Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
|
||||
0 /* when */);
|
||||
final CharSequence subtitleFormatString =
|
||||
mContext.getText(R.string.permission_request_notification_subtitle);
|
||||
n.setLatestEventInfo(mContext,
|
||||
mContext.getText(R.string.permission_request_notification_title),
|
||||
String.format(subtitleFormatString.toString(), account.mName),
|
||||
PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
|
||||
((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.notify(getCredentialPermissionNotificationId(account, authTokenType, uid), n);
|
||||
}
|
||||
|
||||
private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
|
||||
AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
|
||||
RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo =
|
||||
mAuthenticatorCache.getServiceInfo(
|
||||
AuthenticatorDescription.newKey(account.mType));
|
||||
if (serviceInfo == null) {
|
||||
throw new IllegalArgumentException("unknown account type: " + account.mType);
|
||||
}
|
||||
|
||||
final Context authContext;
|
||||
try {
|
||||
authContext = mContext.createPackageContext(
|
||||
serviceInfo.type.packageName, 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
throw new IllegalArgumentException("unknown account type: " + account.mType);
|
||||
}
|
||||
|
||||
Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addCategory(
|
||||
String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid)));
|
||||
intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account);
|
||||
intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL, authTokenLabel);
|
||||
intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType);
|
||||
intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response);
|
||||
intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT_TYPE_LABEL,
|
||||
authContext.getString(serviceInfo.type.labelId));
|
||||
intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_PACKAGES,
|
||||
mContext.getPackageManager().getPackagesForUid(uid));
|
||||
intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private Integer getCredentialPermissionNotificationId(Account account, String authTokenType,
|
||||
int uid) {
|
||||
Integer id;
|
||||
synchronized(mCredentialsPermissionNotificationIds) {
|
||||
final Pair<Pair<Account, String>, Integer> key =
|
||||
new Pair<Pair<Account, String>, Integer>(
|
||||
new Pair<Account, String>(account, authTokenType), uid);
|
||||
id = mCredentialsPermissionNotificationIds.get(key);
|
||||
if (id == null) {
|
||||
id = mNotificationIds.incrementAndGet();
|
||||
mCredentialsPermissionNotificationIds.put(key, id);
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
private Integer getSigninRequiredNotificationId(Account account) {
|
||||
Integer id;
|
||||
synchronized(mSigninRequiredNotificationIds) {
|
||||
id = mSigninRequiredNotificationIds.get(account);
|
||||
if (id == null) {
|
||||
id = mNotificationIds.incrementAndGet();
|
||||
mSigninRequiredNotificationIds.put(account, id);
|
||||
}
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
public void addAcount(final IAccountManagerResponse response, final String accountType,
|
||||
final String authTokenType, final String[] requiredFeatures,
|
||||
final boolean expectActivityLaunch, final Bundle options) {
|
||||
checkManageAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
new Session(response, accountType, expectActivityLaunch) {
|
||||
@@ -579,6 +747,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
|
||||
public void confirmCredentials(IAccountManagerResponse response,
|
||||
final Account account, final boolean expectActivityLaunch) {
|
||||
checkManageAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
new Session(response, account.mType, expectActivityLaunch) {
|
||||
@@ -597,6 +766,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
|
||||
public void confirmPassword(IAccountManagerResponse response, final Account account,
|
||||
final String password) {
|
||||
checkManageAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
new Session(response, account.mType, false /* expectActivityLaunch */) {
|
||||
@@ -616,6 +786,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
public void updateCredentials(IAccountManagerResponse response, final Account account,
|
||||
final String authTokenType, final boolean expectActivityLaunch,
|
||||
final Bundle loginOptions) {
|
||||
checkManageAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
new Session(response, account.mType, expectActivityLaunch) {
|
||||
@@ -637,6 +808,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
|
||||
public void editProperties(IAccountManagerResponse response, final String accountType,
|
||||
final boolean expectActivityLaunch) {
|
||||
checkManageAccountsPermission();
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
new Session(response, accountType, expectActivityLaunch) {
|
||||
@@ -728,6 +900,7 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
}
|
||||
public void getAccountsByTypeAndFeatures(IAccountManagerResponse response,
|
||||
String type, String[] features) {
|
||||
checkReadAccountsPermission();
|
||||
if (type == null) {
|
||||
if (response != null) {
|
||||
try {
|
||||
@@ -929,7 +1102,12 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
public void onResult(Bundle result) {
|
||||
mNumResults++;
|
||||
if (result != null && !TextUtils.isEmpty(result.getString(Constants.AUTHTOKEN_KEY))) {
|
||||
cancelNotification();
|
||||
String accountName = result.getString(Constants.ACCOUNT_NAME_KEY);
|
||||
String accountType = result.getString(Constants.ACCOUNT_TYPE_KEY);
|
||||
if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
|
||||
Account account = new Account(accountName, accountType);
|
||||
cancelNotification(getSigninRequiredNotificationId(account));
|
||||
}
|
||||
}
|
||||
IAccountManagerResponse response;
|
||||
if (mExpectActivityLaunch && result != null
|
||||
@@ -1026,6 +1204,8 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
+ AUTHTOKENS_AUTHTOKEN + " TEXT, "
|
||||
+ "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
|
||||
|
||||
createGrantsTable(db);
|
||||
|
||||
db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
|
||||
+ EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||
+ EXTRAS_ACCOUNTS_ID + " INTEGER, "
|
||||
@@ -1037,6 +1217,10 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
+ META_KEY + " TEXT PRIMARY KEY NOT NULL, "
|
||||
+ META_VALUE + " TEXT)");
|
||||
|
||||
createAccountsDeletionTrigger(db);
|
||||
}
|
||||
|
||||
private void createAccountsDeletionTrigger(SQLiteDatabase db) {
|
||||
db.execSQL(""
|
||||
+ " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
|
||||
+ " BEGIN"
|
||||
@@ -1044,22 +1228,34 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
+ " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
|
||||
+ " DELETE FROM " + TABLE_EXTRAS
|
||||
+ " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
|
||||
+ " DELETE FROM " + TABLE_GRANTS
|
||||
+ " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
|
||||
+ " END");
|
||||
}
|
||||
|
||||
private void createGrantsTable(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
|
||||
+ GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
|
||||
+ GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
|
||||
+ GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
|
||||
+ "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
|
||||
+ "," + GRANTS_GRANTEE_UID + "))");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
|
||||
|
||||
if (oldVersion == 1) {
|
||||
db.execSQL(""
|
||||
+ " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
|
||||
+ " BEGIN"
|
||||
+ " DELETE FROM " + TABLE_AUTHTOKENS
|
||||
+ " WHERE " + AUTHTOKENS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
|
||||
+ " DELETE FROM " + TABLE_EXTRAS
|
||||
+ " WHERE " + EXTRAS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
|
||||
+ " END");
|
||||
// no longer need to do anything since the work is done
|
||||
// when upgrading from version 2
|
||||
oldVersion++;
|
||||
}
|
||||
|
||||
if (oldVersion == 2) {
|
||||
createGrantsTable(db);
|
||||
db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
|
||||
createAccountsDeletionTrigger(db);
|
||||
oldVersion++;
|
||||
}
|
||||
}
|
||||
@@ -1156,31 +1352,171 @@ public class AccountManagerService extends IAccountManager.Stub {
|
||||
mAuthenticatorCache.dump(fd, fout, args);
|
||||
}
|
||||
|
||||
private void doNotification(CharSequence message, Intent intent) {
|
||||
private void doNotification(Account account, CharSequence message, Intent intent) {
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||
Log.v(TAG, "doNotification: " + message + " intent:" + intent);
|
||||
}
|
||||
|
||||
Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
|
||||
0 /* when */);
|
||||
n.setLatestEventInfo(mContext, mContext.getText(R.string.notification_title), message,
|
||||
PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
|
||||
((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.notify(NOTIFICATION_ID, n);
|
||||
if (intent.getComponent() != null &&
|
||||
GrantCredentialsPermissionActivity.class.getName().equals(
|
||||
intent.getComponent().getClassName())) {
|
||||
createNoCredentialsPermissionNotification(account, intent);
|
||||
} else {
|
||||
Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
|
||||
0 /* when */);
|
||||
n.setLatestEventInfo(mContext, mContext.getText(R.string.notification_title),
|
||||
message, PendingIntent.getActivity(
|
||||
mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
|
||||
((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.notify(getSigninRequiredNotificationId(account), n);
|
||||
}
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelNotification() {
|
||||
private void cancelNotification(int id) {
|
||||
long identityToken = clearCallingIdentity();
|
||||
try {
|
||||
((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.cancel(NOTIFICATION_ID);
|
||||
.cancel(id);
|
||||
} finally {
|
||||
restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkBinderPermission(String permission) {
|
||||
final int uid = Binder.getCallingUid();
|
||||
if (mContext.checkCallingOrSelfPermission(permission) !=
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
String msg = "caller uid " + uid + " lacks " + permission;
|
||||
Log.w(TAG, msg);
|
||||
throw new SecurityException(msg);
|
||||
}
|
||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||
Log.v(TAG, "caller uid " + uid + " has " + permission);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
|
||||
final boolean fromAuthenticator = hasAuthenticatorUid(account.mType, callerUid);
|
||||
final boolean hasExplicitGrants = hasExplicitlyGrantedPermission(account, authTokenType);
|
||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||
Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
|
||||
+ callerUid + ", account " + account
|
||||
+ ": is authenticator? " + fromAuthenticator
|
||||
+ ", has explicit permission? " + hasExplicitGrants);
|
||||
}
|
||||
return fromAuthenticator || hasExplicitGrants;
|
||||
}
|
||||
|
||||
private boolean hasAuthenticatorUid(String accountType, int callingUid) {
|
||||
for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
|
||||
mAuthenticatorCache.getAllServices()) {
|
||||
if (serviceInfo.type.type.equals(accountType)) {
|
||||
return serviceInfo.uid == callingUid;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType) {
|
||||
if (Binder.getCallingUid() == android.os.Process.SYSTEM_UID) {
|
||||
return true;
|
||||
}
|
||||
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
|
||||
String[] args = {String.valueOf(Binder.getCallingUid()), authTokenType,
|
||||
account.mName, account.mType};
|
||||
final boolean permissionGranted =
|
||||
DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0;
|
||||
if (!permissionGranted && isDebuggableMonkeyBuild) {
|
||||
// TODO: Skip this check when running automated tests. Replace this
|
||||
// with a more general solution.
|
||||
Log.w(TAG, "no credentials permission for usage of " + account + ", "
|
||||
+ authTokenType + " by uid " + Binder.getCallingUid()
|
||||
+ " but ignoring since this is a monkey build");
|
||||
return true;
|
||||
}
|
||||
return permissionGranted;
|
||||
}
|
||||
|
||||
private void checkCallingUidAgainstAuthenticator(Account account) {
|
||||
final int uid = Binder.getCallingUid();
|
||||
if (!hasAuthenticatorUid(account.mType, uid)) {
|
||||
String msg = "caller uid " + uid + " is different than the authenticator's uid";
|
||||
Log.w(TAG, msg);
|
||||
throw new SecurityException(msg);
|
||||
}
|
||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||
Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid");
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAuthenticateAccountsPermission(Account account) {
|
||||
checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS);
|
||||
checkCallingUidAgainstAuthenticator(account);
|
||||
}
|
||||
|
||||
private void checkReadAccountsPermission() {
|
||||
checkBinderPermission(Manifest.permission.GET_ACCOUNTS);
|
||||
}
|
||||
|
||||
private void checkManageAccountsPermission() {
|
||||
checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow callers with the given uid permission to get credentials for account/authTokenType.
|
||||
* <p>
|
||||
* Although this is public it can only be accessed via the AccountManagerService object
|
||||
* which is in the system. This means we don't need to protect it with permissions.
|
||||
* @hide
|
||||
*/
|
||||
public void grantAppPermission(Account account, String authTokenType, int uid) {
|
||||
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
long accountId = getAccountId(db, account);
|
||||
if (accountId >= 0) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(GRANTS_ACCOUNTS_ID, accountId);
|
||||
values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
|
||||
values.put(GRANTS_GRANTEE_UID, uid);
|
||||
db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
|
||||
db.setTransactionSuccessful();
|
||||
}
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't allow callers with the given uid permission to get credentials for
|
||||
* account/authTokenType.
|
||||
* <p>
|
||||
* Although this is public it can only be accessed via the AccountManagerService object
|
||||
* which is in the system. This means we don't need to protect it with permissions.
|
||||
* @hide
|
||||
*/
|
||||
public void revokeAppPermission(Account account, String authTokenType, int uid) {
|
||||
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
db.beginTransaction();
|
||||
try {
|
||||
long accountId = getAccountId(db, account);
|
||||
if (accountId >= 0) {
|
||||
db.delete(TABLE_GRANTS,
|
||||
GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
|
||||
+ GRANTS_GRANTEE_UID + "=?",
|
||||
new String[]{String.valueOf(accountId), authTokenType,
|
||||
String.valueOf(uid)});
|
||||
db.setTransactionSuccessful();
|
||||
}
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ public class Constants {
|
||||
public static final String ACCOUNT_AUTHENTICATOR_RESPONSE_KEY = "accountAuthenticatorResponse";
|
||||
public static final String ACCOUNT_MANAGER_RESPONSE_KEY = "accountManagerResponse";
|
||||
public static final String AUTH_FAILED_MESSAGE_KEY = "authFailedMessage";
|
||||
public static final String AUTH_TOKEN_LABEL_KEY = "authTokenLabelKey";
|
||||
|
||||
public static final String AUTHENTICATOR_INTENT_ACTION =
|
||||
"android.accounts.AccountAuthenticator";
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package android.accounts;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.view.View;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import com.android.internal.R;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class GrantCredentialsPermissionActivity extends Activity implements View.OnClickListener {
|
||||
public static final String EXTRAS_ACCOUNT = "account";
|
||||
public static final String EXTRAS_AUTH_TOKEN_LABEL = "authTokenLabel";
|
||||
public static final String EXTRAS_AUTH_TOKEN_TYPE = "authTokenType";
|
||||
public static final String EXTRAS_RESPONSE = "response";
|
||||
public static final String EXTRAS_ACCOUNT_TYPE_LABEL = "accountTypeLabel";
|
||||
public static final String EXTRAS_PACKAGES = "application";
|
||||
public static final String EXTRAS_REQUESTING_UID = "uid";
|
||||
private Account mAccount;
|
||||
private String mAuthTokenType;
|
||||
private int mUid;
|
||||
private Bundle mResultBundle = null;
|
||||
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getWindow().setContentView(R.layout.grant_credentials_permission);
|
||||
mAccount = getIntent().getExtras().getParcelable(EXTRAS_ACCOUNT);
|
||||
mAuthTokenType = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_TYPE);
|
||||
mUid = getIntent().getExtras().getInt(EXTRAS_REQUESTING_UID);
|
||||
final String accountTypeLabel =
|
||||
getIntent().getExtras().getString(EXTRAS_ACCOUNT_TYPE_LABEL);
|
||||
final String[] packages = getIntent().getExtras().getStringArray(EXTRAS_PACKAGES);
|
||||
|
||||
findViewById(R.id.allow).setOnClickListener(this);
|
||||
findViewById(R.id.deny).setOnClickListener(this);
|
||||
|
||||
TextView messageView = (TextView) getWindow().findViewById(R.id.message);
|
||||
String authTokenLabel = getIntent().getExtras().getString(EXTRAS_AUTH_TOKEN_LABEL);
|
||||
if (authTokenLabel.length() == 0) {
|
||||
CharSequence grantCredentialsPermissionFormat = getResources().getText(
|
||||
R.string.grant_credentials_permission_message_desc);
|
||||
messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
|
||||
mAccount.mName, accountTypeLabel));
|
||||
} else {
|
||||
CharSequence grantCredentialsPermissionFormat = getResources().getText(
|
||||
R.string.grant_credentials_permission_message_with_authtokenlabel_desc);
|
||||
messageView.setText(String.format(grantCredentialsPermissionFormat.toString(),
|
||||
authTokenLabel, mAccount.mName, accountTypeLabel));
|
||||
}
|
||||
|
||||
String[] packageLabels = new String[packages.length];
|
||||
final PackageManager pm = getPackageManager();
|
||||
for (int i = 0; i < packages.length; i++) {
|
||||
try {
|
||||
packageLabels[i] =
|
||||
pm.getApplicationLabel(pm.getApplicationInfo(packages[i], 0)).toString();
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
packageLabels[i] = packages[i];
|
||||
}
|
||||
}
|
||||
((ListView) findViewById(R.id.packages_list)).setAdapter(
|
||||
new PackagesArrayAdapter(this, packageLabels));
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.allow:
|
||||
AccountManagerService.getSingleton().grantAppPermission(mAccount, mAuthTokenType,
|
||||
mUid);
|
||||
Intent result = new Intent();
|
||||
result.putExtra("retry", true);
|
||||
setResult(RESULT_OK, result);
|
||||
setAccountAuthenticatorResult(result.getExtras());
|
||||
break;
|
||||
|
||||
case R.id.deny:
|
||||
AccountManagerService.getSingleton().revokeAppPermission(mAccount, mAuthTokenType,
|
||||
mUid);
|
||||
setResult(RESULT_CANCELED);
|
||||
break;
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
public final void setAccountAuthenticatorResult(Bundle result) {
|
||||
mResultBundle = result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the result or a Constants.ERROR_CODE_CANCELED error if a result isn't present.
|
||||
*/
|
||||
public void finish() {
|
||||
Intent intent = getIntent();
|
||||
AccountAuthenticatorResponse accountAuthenticatorResponse =
|
||||
intent.getParcelableExtra(EXTRAS_RESPONSE);
|
||||
if (accountAuthenticatorResponse != null) {
|
||||
// send the result bundle back if set, otherwise send an error.
|
||||
if (mResultBundle != null) {
|
||||
accountAuthenticatorResponse.onResult(mResultBundle);
|
||||
} else {
|
||||
accountAuthenticatorResponse.onError(Constants.ERROR_CODE_CANCELED, "canceled");
|
||||
}
|
||||
}
|
||||
super.finish();
|
||||
}
|
||||
|
||||
private static class PackagesArrayAdapter extends ArrayAdapter<String> {
|
||||
protected LayoutInflater mInflater;
|
||||
private static final int mResource = R.layout.simple_list_item_1;
|
||||
|
||||
public PackagesArrayAdapter(Context context, String[] items) {
|
||||
super(context, mResource, items);
|
||||
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
}
|
||||
|
||||
static class ViewHolder {
|
||||
TextView label;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
// A ViewHolder keeps references to children views to avoid unneccessary calls
|
||||
// to findViewById() on each row.
|
||||
ViewHolder holder;
|
||||
|
||||
// When convertView is not null, we can reuse it directly, there is no need
|
||||
// to reinflate it. We only inflate a new View when the convertView supplied
|
||||
// by ListView is null.
|
||||
if (convertView == null) {
|
||||
convertView = mInflater.inflate(mResource, null);
|
||||
|
||||
// Creates a ViewHolder and store references to the two children views
|
||||
// we want to bind data to.
|
||||
holder = new ViewHolder();
|
||||
holder.label = (TextView) convertView.findViewById(R.id.text1);
|
||||
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
// Get the ViewHolder back to get fast access to the TextView
|
||||
// and the ImageView.
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
}
|
||||
|
||||
holder.label.setText(getItem(position));
|
||||
|
||||
return convertView;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,11 @@ oneway interface IAccountAuthenticator {
|
||||
void getAuthToken(in IAccountAuthenticatorResponse response, in Account account,
|
||||
String authTokenType, in Bundle options);
|
||||
|
||||
/**
|
||||
* Gets the user-visible label of the given authtoken type.
|
||||
*/
|
||||
void getAuthTokenLabel(in IAccountAuthenticatorResponse response, String authTokenType);
|
||||
|
||||
/**
|
||||
* prompts the user for a new password and writes it to the IAccountManager
|
||||
*/
|
||||
|
||||
@@ -114,10 +114,12 @@ public abstract class RegisteredServicesCache<V> {
|
||||
public static class ServiceInfo<V> {
|
||||
public final V type;
|
||||
public final ComponentName componentName;
|
||||
public final int uid;
|
||||
|
||||
private ServiceInfo(V type, ComponentName componentName) {
|
||||
private ServiceInfo(V type, ComponentName componentName, int uid) {
|
||||
this.type = type;
|
||||
this.componentName = componentName;
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@@ -223,7 +225,10 @@ public abstract class RegisteredServicesCache<V> {
|
||||
if (v == null) {
|
||||
return null;
|
||||
}
|
||||
return new ServiceInfo<V>(v, componentName);
|
||||
final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
|
||||
final ApplicationInfo applicationInfo = serviceInfo.applicationInfo;
|
||||
final int uid = applicationInfo.uid;
|
||||
return new ServiceInfo<V>(v, componentName, uid);
|
||||
} finally {
|
||||
if (parser != null) parser.close();
|
||||
}
|
||||
|
||||
76
core/java/android/util/Pair.java
Normal file
76
core/java/android/util/Pair.java
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (C) 2009 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.util;
|
||||
|
||||
/**
|
||||
* Container to ease passing around a tuple of two objects. This object provides a sensible
|
||||
* implementation of equals(), returning true if equals() is true on each of the contained
|
||||
* objects.
|
||||
*/
|
||||
public class Pair<F, S> {
|
||||
public final F first;
|
||||
public final S second;
|
||||
|
||||
/**
|
||||
* Constructor for a Pair. If either are null then equals() and hashCode() will throw
|
||||
* a NullPointerException.
|
||||
* @param first the first object in the Pair
|
||||
* @param second the second object in the pair
|
||||
*/
|
||||
public Pair(F first, S second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the two objects for equality by delegating to their respective equals() methods.
|
||||
* @param o the Pair to which this one is to be checked for equality
|
||||
* @return true if the underlying objects of the Pair are both considered equals()
|
||||
*/
|
||||
public boolean equals(Object o) {
|
||||
if (o == this) return true;
|
||||
if (!(o instanceof Pair)) return false;
|
||||
final Pair<F, S> other;
|
||||
try {
|
||||
other = (Pair<F, S>) o;
|
||||
} catch (ClassCastException e) {
|
||||
return false;
|
||||
}
|
||||
return first.equals(other.first) && second.equals(other.second);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hash code using the hash codes of the underlying objects
|
||||
* @return a hashcode of the Pair
|
||||
*/
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
result = 31 * result + first.hashCode();
|
||||
result = 31 * result + second.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for creating an appropriately typed pair.
|
||||
* @param a the first object in the Pair
|
||||
* @param b the second object in the pair
|
||||
* @return a Pair that is templatized with the types of a and b
|
||||
*/
|
||||
public static <A, B> Pair <A, B> create(A a, B b) {
|
||||
return new Pair<A, B>(a, b);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
<!-- Special broadcasts that only the system can send -->
|
||||
<!-- ================================================ -->
|
||||
<eat-comment />
|
||||
|
||||
|
||||
<protected-broadcast android:name="android.intent.action.SCREEN_OFF" />
|
||||
<protected-broadcast android:name="android.intent.action.SCREEN_ON" />
|
||||
<protected-broadcast android:name="android.intent.action.USER_PRESENT" />
|
||||
@@ -52,7 +52,7 @@
|
||||
<protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
|
||||
<protected-broadcast android:name="android.intent.action.NEW_OUTGOING_CALL" />
|
||||
<protected-broadcast android:name="android.intent.action.REBOOT" />
|
||||
|
||||
|
||||
<!-- ====================================== -->
|
||||
<!-- Permissions for things that cost money -->
|
||||
<!-- ====================================== -->
|
||||
@@ -309,6 +309,14 @@
|
||||
android:description="@string/permdesc_bluetooth"
|
||||
android:label="@string/permlab_bluetooth" />
|
||||
|
||||
<!-- Allows applications to call into AccountAuthenticators. Only
|
||||
the system can get this permission. -->
|
||||
<permission android:name="android.permission.ACCOUNT_MANAGER_SERVICE"
|
||||
android:permissionGroup="android.permission-group.ACCOUNTS"
|
||||
android:protectionLevel="signature"
|
||||
android:description="@string/permdesc_accountManagerService"
|
||||
android:label="@string/permlab_accountManagerService" />
|
||||
|
||||
<!-- ================================== -->
|
||||
<!-- Permissions for accessing accounts -->
|
||||
<!-- ================================== -->
|
||||
@@ -330,6 +338,28 @@
|
||||
android:description="@string/permdesc_getAccounts"
|
||||
android:label="@string/permlab_getAccounts" />
|
||||
|
||||
<!-- Allows an application to act as an AccountAuthenticator for
|
||||
the AccountManager -->
|
||||
<permission android:name="android.permission.AUTHENTICATE_ACCOUNTS"
|
||||
android:permissionGroup="android.permission-group.ACCOUNTS"
|
||||
android:protectionLevel="dangerous"
|
||||
android:label="@string/permlab_authenticateAccounts"
|
||||
android:description="@string/permdesc_authenticateAccounts" />
|
||||
|
||||
<!-- Allows an application to request authtokens from the AccountManager -->
|
||||
<permission android:name="android.permission.USE_CREDENTIALS"
|
||||
android:permissionGroup="android.permission-group.ACCOUNTS"
|
||||
android:protectionLevel="dangerous"
|
||||
android:label="@string/permlab_useCredentials"
|
||||
android:description="@string/permdesc_useCredentials" />
|
||||
|
||||
<!-- Allows an application to manage the list of accounts in the AccountManager -->
|
||||
<permission android:name="android.permission.MANAGE_ACCOUNTS"
|
||||
android:permissionGroup="android.permission-group.ACCOUNTS"
|
||||
android:protectionLevel="dangerous"
|
||||
android:label="@string/permlab_manageAccounts"
|
||||
android:description="@string/permdesc_manageAccounts" />
|
||||
|
||||
<!-- ================================== -->
|
||||
<!-- Permissions for accessing hardware -->
|
||||
<!-- ================================== -->
|
||||
@@ -1093,6 +1123,11 @@
|
||||
android:exported="true">
|
||||
</activity>
|
||||
|
||||
<activity android:name="android.accounts.GrantCredentialsPermissionActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true">
|
||||
</activity>
|
||||
|
||||
<service android:name="com.android.server.LoadAverageService"
|
||||
android:exported="true" />
|
||||
|
||||
|
||||
41
core/res/res/layout/grant_credentials_permission.xml
Normal file
41
core/res/res/layout/grant_credentials_permission.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
/*
|
||||
** Copyright 2007, The Android Open Source Project
|
||||
**
|
||||
** Licensed under the Apache License, Version 2.0 (the "License");
|
||||
** you may not use this file except in compliance with the License.
|
||||
** You may obtain a copy of the License at
|
||||
**
|
||||
** http://www.apache.org/licenses/LICENSE-2.0
|
||||
**
|
||||
** Unless required by applicable law or agreed to in writing, software
|
||||
** distributed under the License is distributed on an "AS IS" BASIS,
|
||||
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
** See the License for the specific language governing permissions and
|
||||
** limitations under the License.
|
||||
*/
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/message" />
|
||||
<Button android:id="@+id/allow"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/allow" />
|
||||
|
||||
<Button android:id="@+id/deny"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/deny" />
|
||||
|
||||
<ListView android:id="@+id/packages_list"
|
||||
android:layout_width="fill_parent" android:layout_height="fill_parent"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -809,7 +809,7 @@
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_readFrameBuffer">read frame buffer</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_readFrameBuffer">Allows application to
|
||||
<string name="permdesc_readFrameBuffer">Allows application to
|
||||
read the content of the frame buffer.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
@@ -969,12 +969,40 @@
|
||||
<string name="permdesc_setTimeZone">Allows an application to change
|
||||
the phone\'s time zone.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_accountManagerService">act as the AccountManagerService</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_accountManagerService">Allows an
|
||||
application to make calls to AccountAuthenticators</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_getAccounts">discover known accounts</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_getAccounts">Allows an application to get
|
||||
the list of accounts known by the phone.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_authenticateAccounts">act as an account authenticator</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_authenticateAccounts">Allows an application
|
||||
to use the account authenticator capabilities of the
|
||||
AccountManager, including creating accounts and getting and
|
||||
setting their passwords.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_manageAccounts">manage the accounts list</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_manageAccounts">Allows an application to
|
||||
perform operations like adding, and removing accounts and deleting
|
||||
their password.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_useCredentials">use the authentication
|
||||
credentials of an account</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permdesc_useCredentials">Allows an application to
|
||||
request authentication tokens.</string>
|
||||
|
||||
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
<string name="permlab_accessNetworkState">view network state</string>
|
||||
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
|
||||
@@ -1797,7 +1825,7 @@
|
||||
<string name="adb_active_notification_title">USB debugging connected</string>
|
||||
<!-- Message of notification shown when ADB is actively connected to the phone. -->
|
||||
<string name="adb_active_notification_message">A computer is connected to your phone.</string>
|
||||
|
||||
|
||||
<!-- Used to replace %s in urls retreived from the signin server with locales. For Some -->
|
||||
<!-- devices we don't support all the locales we ship to and need to replace the '%s' with a -->
|
||||
<!-- locale string based on mcc values. By default (0-length string) we don't replace the %s -->
|
||||
@@ -2048,4 +2076,19 @@
|
||||
<!-- Title for the unselected state of a CompoundButton. -->
|
||||
<string name="accessibility_compound_button_unselected">not checked</string>
|
||||
|
||||
<string name="grant_credentials_permission_message_desc">The
|
||||
listed applications are requesting permission to access the login credentials for account %s from
|
||||
%s. Do you wish to grant this permission? If so then your answer will be remembered and you will not be prompted
|
||||
again.</string>
|
||||
|
||||
<string name="grant_credentials_permission_message_with_authtokenlabel_desc">The
|
||||
listed applications are requesting permission to access the %s login credentials for account %s from
|
||||
%s. Do you wish to grant this permission? If so then your answer will be remembered and you will not be prompted
|
||||
again.</string>
|
||||
|
||||
<string name="allow">Allow</string>
|
||||
<string name="deny">Deny</string>
|
||||
<string name="permission_request_notification_title">Permission Requested</string>
|
||||
<string name="permission_request_notification_subtitle">for account %s</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
|
||||
<uses-permission android:name="com.android.unit_tests.permission.TEST_GRANTED" />
|
||||
|
||||
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
|
||||
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
|
||||
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.ALL_SERVICES" />
|
||||
<uses-permission android:name="com.google.android.googleapps.permission.ACCESS_GOOGLE_PASSWORD" />
|
||||
|
||||
Reference in New Issue
Block a user