How to get device email address in Delphi 10

2019-01-15 12:12发布

问题:

I'm trying to get my device email address, I used Java2OP to convert AccountManager class to object pascal. However, I tried to get the email address using the following code:

 jAm: JAccountManager;
 accounts: TJavaObjectArray<JAccountClass>;
 jAcc: JAccountClass;
 begin

  jAM := TJAccountManager.JavaClass.get(SharedActivityContext);
  accounts := TJavaObjectArray<JAccountClass>.Wrap(jAM.getAccountsByType(StringToJString('com.google')));

  mmLog.Lines.Add('Length Accounts: ' + Inttostr(accounts.Length));

  if accounts.Length > 0 then begin
     jAcc := accounts.Items[0];
     mmLog.Lines.Add(jstringtostring( jAcc.name));
  end else begin
     mmLog.Lines.Add('no accounts available');
  end;

I get Access violation at address 415E5254, accessing address 0000002C! any idea guys?

 JAccountClass = interface(JObjectClass)
['{94EE6861-F326-489F-8919-E20B39E3D9C1}']
{class} function _GetCREATOR: JParcelable_Creator; cdecl;
{class} function _Getname: JString; cdecl;
{class} function _Gettype: JString; cdecl;
{class} function init(name: JString; &type: JString): JAccount; cdecl; overload;//Deprecated
{class} function init(init: JParcel): JAccount; cdecl; overload;//Deprecated
{class} function describeContents: Integer; cdecl;
{class} function equals(o: JObject): Boolean; cdecl;
{class} property CREATOR: JParcelable_Creator read _GetCREATOR;
{class} property name: JString read _Getname;
{class} property &type: JString read _Gettype;
end;

  [JavaSignature('android/accounts/Account')]
 JAccount = interface(JObject)
['{71476381-8B6E-471F-9189-9857ECD7508C}']
function hashCode: Integer; cdecl;
function toString: JString; cdecl;
procedure writeToParcel(dest: JParcel; flags: Integer); cdecl;
end;
TJAccount = class(TJavaGenericImport<JAccountClass, JAccount>) end;

JAccountManagerClass = interface(JObjectClass)
['{96273844-2D84-47F0-BFD5-14B73402F843}']
{class} function _GetACTION_AUTHENTICATOR_INTENT: JString; cdecl;
{class} function _GetAUTHENTICATOR_ATTRIBUTES_NAME: JString; cdecl;
{class} function _GetAUTHENTICATOR_META_DATA_NAME: JString; cdecl;
{class} function _GetERROR_CODE_BAD_ARGUMENTS: Integer; cdecl;
{class} function _GetERROR_CODE_BAD_AUTHENTICATION: Integer; cdecl;
{class} function _GetERROR_CODE_BAD_REQUEST: Integer; cdecl;
{class} function _GetERROR_CODE_CANCELED: Integer; cdecl;
{class} function _GetERROR_CODE_INVALID_RESPONSE: Integer; cdecl;
{class} function _GetERROR_CODE_NETWORK_ERROR: Integer; cdecl;
{class} function _GetERROR_CODE_REMOTE_EXCEPTION: Integer; cdecl;
{class} function _GetERROR_CODE_UNSUPPORTED_OPERATION: Integer; cdecl;
{class} function _GetKEY_ACCOUNTS: JString; cdecl;
{class} function _GetKEY_ACCOUNT_AUTHENTICATOR_RESPONSE: JString; cdecl;
{class} function _GetKEY_ACCOUNT_MANAGER_RESPONSE: JString; cdecl;
{class} function _GetKEY_ACCOUNT_NAME: JString; cdecl;
{class} function _GetKEY_ACCOUNT_TYPE: JString; cdecl;
{class} function _GetKEY_ANDROID_PACKAGE_NAME: JString; cdecl;
{class} function _GetKEY_AUTHENTICATOR_TYPES: JString; cdecl;
{class} function _GetKEY_AUTHTOKEN: JString; cdecl;
{class} function _GetKEY_AUTH_FAILED_MESSAGE: JString; cdecl;
{class} function _GetKEY_AUTH_TOKEN_LABEL: JString; cdecl;
{class} function _GetKEY_BOOLEAN_RESULT: JString; cdecl;
{class} function _GetKEY_CALLER_PID: JString; cdecl;
{class} function _GetKEY_CALLER_UID: JString; cdecl;
{class} function _GetKEY_ERROR_CODE: JString; cdecl;
{class} function _GetKEY_ERROR_MESSAGE: JString; cdecl;
{class} function _GetKEY_INTENT: JString; cdecl;
{class} function _GetKEY_PASSWORD: JString; cdecl;
{class} function _GetKEY_USERDATA: JString; cdecl;
{class} function _GetLOGIN_ACCOUNTS_CHANGED_ACTION: JString; cdecl;
{class} function addAccount(accountType: JString; authTokenType: JString; requiredFeatures: TJavaObjectArray<JString>; addAccountOptions: JBundle; activity: JActivity; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl;//Deprecated
{class} procedure clearPassword(account: JAccount); cdecl;//Deprecated
{class} function confirmCredentials(account: JAccount; options: JBundle; activity: JActivity; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl;//Deprecated
{class} function editProperties(accountType: JString; activity: JActivity; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl;//Deprecated
{class} function get(context: JContext): JAccountManager; cdecl;
{class} function getAccountsByTypeAndFeatures(&type: JString; features: TJavaObjectArray<JString>; callback: TJavaObjectArray<JAccountManagerCallback>; handler: JHandler): TJavaObjectArray<JAccountManagerFuture>; cdecl;
{class} function getAccountsByTypeForPackage(&type: JString; packageName: JString): TJavaObjectArray<JAccount>; cdecl;
{class} function getAuthToken(account: JAccount; authTokenType: JString; options: JBundle; activity: JActivity; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl; overload;
{class} function getAuthenticatorTypes: TJavaObjectArray<JAuthenticatorDescription>; cdecl;
{class} function getPassword(account: JAccount): JString; cdecl;
{class} function getUserData(account: JAccount; key: JString): JString; cdecl;
{class} function newChooseAccountIntent(selectedAccount: JAccount; allowableAccounts: JArrayList; allowableAccountTypes: TJavaObjectArray<JString>; alwaysPromptForAccount: Boolean; descriptionOverrideText: JString; addAccountAuthTokenType: JString; addAccountRequiredFeatures: TJavaObjectArray<JString>; addAccountOptions: JBundle): JIntent; cdecl;
{class} function peekAuthToken(account: JAccount; authTokenType: JString): JString; cdecl;
{class} function removeAccount(account: JAccount; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl;
{class} procedure removeOnAccountsUpdatedListener(listener: JOnAccountsUpdateListener); cdecl;
{class} function updateCredentials(account: JAccount; authTokenType: JString; options: JBundle; activity: JActivity; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl;
{class} property ACTION_AUTHENTICATOR_INTENT: JString read _GetACTION_AUTHENTICATOR_INTENT;
{class} property AUTHENTICATOR_ATTRIBUTES_NAME: JString read _GetAUTHENTICATOR_ATTRIBUTES_NAME;
{class} property AUTHENTICATOR_META_DATA_NAME: JString read _GetAUTHENTICATOR_META_DATA_NAME;
{class} property ERROR_CODE_BAD_ARGUMENTS: Integer read _GetERROR_CODE_BAD_ARGUMENTS;
{class} property ERROR_CODE_BAD_AUTHENTICATION: Integer read _GetERROR_CODE_BAD_AUTHENTICATION;
{class} property ERROR_CODE_BAD_REQUEST: Integer read _GetERROR_CODE_BAD_REQUEST;
{class} property ERROR_CODE_CANCELED: Integer read _GetERROR_CODE_CANCELED;
{class} property ERROR_CODE_INVALID_RESPONSE: Integer read _GetERROR_CODE_INVALID_RESPONSE;
{class} property ERROR_CODE_NETWORK_ERROR: Integer read _GetERROR_CODE_NETWORK_ERROR;
{class} property ERROR_CODE_REMOTE_EXCEPTION: Integer read _GetERROR_CODE_REMOTE_EXCEPTION;
{class} property ERROR_CODE_UNSUPPORTED_OPERATION: Integer read _GetERROR_CODE_UNSUPPORTED_OPERATION;
{class} property KEY_ACCOUNTS: JString read _GetKEY_ACCOUNTS;
{class} property KEY_ACCOUNT_AUTHENTICATOR_RESPONSE: JString read _GetKEY_ACCOUNT_AUTHENTICATOR_RESPONSE;
{class} property KEY_ACCOUNT_MANAGER_RESPONSE: JString read _GetKEY_ACCOUNT_MANAGER_RESPONSE;
{class} property KEY_ACCOUNT_NAME: JString read _GetKEY_ACCOUNT_NAME;
{class} property KEY_ACCOUNT_TYPE: JString read _GetKEY_ACCOUNT_TYPE;
{class} property KEY_ANDROID_PACKAGE_NAME: JString read _GetKEY_ANDROID_PACKAGE_NAME;
{class} property KEY_AUTHENTICATOR_TYPES: JString read _GetKEY_AUTHENTICATOR_TYPES;
{class} property KEY_AUTHTOKEN: JString read _GetKEY_AUTHTOKEN;
{class} property KEY_AUTH_FAILED_MESSAGE: JString read _GetKEY_AUTH_FAILED_MESSAGE;
{class} property KEY_AUTH_TOKEN_LABEL: JString read _GetKEY_AUTH_TOKEN_LABEL;
{class} property KEY_BOOLEAN_RESULT: JString read _GetKEY_BOOLEAN_RESULT;
{class} property KEY_CALLER_PID: JString read _GetKEY_CALLER_PID;
{class} property KEY_CALLER_UID: JString read _GetKEY_CALLER_UID;
{class} property KEY_ERROR_CODE: JString read _GetKEY_ERROR_CODE;
{class} property KEY_ERROR_MESSAGE: JString read _GetKEY_ERROR_MESSAGE;
{class} property KEY_INTENT: JString read _GetKEY_INTENT;
{class} property KEY_PASSWORD: JString read _GetKEY_PASSWORD;
{class} property KEY_USERDATA: JString read _GetKEY_USERDATA;
{class} property LOGIN_ACCOUNTS_CHANGED_ACTION: JString read _GetLOGIN_ACCOUNTS_CHANGED_ACTION;
end;

[JavaSignature('android/accounts/AccountManager')]
JAccountManager = interface(JObject)
['{9FA4077B-4628-433C-BAFC-9EB299DA9C98}']
function addAccountExplicitly(account: JAccount; password: JString; userdata: JBundle): Boolean; cdecl;//Deprecated
procedure addOnAccountsUpdatedListener(listener: JOnAccountsUpdateListener; handler: JHandler; updateImmediately: Boolean); cdecl;//Deprecated
function blockingGetAuthToken(account: JAccount; authTokenType: JString; notifyAuthFailure: Boolean): JString; cdecl;//Deprecated
function getAccounts: TJavaObjectArray<JAccount>; cdecl;
function getAccountsByType(&type: JString): TJavaObjectArray<JAccount>; cdecl;
function getAuthToken(account: JAccount; authTokenType: JString; notifyAuthFailure: Boolean; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl; overload;//Deprecated
function getAuthToken(account: JAccount; authTokenType: JString; options: JBundle; notifyAuthFailure: Boolean; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl; overload;
function getAuthTokenByFeatures(accountType: JString; authTokenType: JString; features: TJavaObjectArray<JString>; activity: JActivity; addAccountOptions: JBundle; getAuthTokenOptions: JBundle; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl;
function hasFeatures(account: JAccount; features: TJavaObjectArray<JString>; callback: JAccountManagerCallback; handler: JHandler): JAccountManagerFuture; cdecl;
procedure invalidateAuthToken(accountType: JString; authToken: JString); cdecl;
procedure setAuthToken(account: JAccount; authTokenType: JString; authToken: JString); cdecl;
procedure setPassword(account: JAccount; password: JString); cdecl;
procedure setUserData(account: JAccount; key: JString; value: JString); cdecl;
end;
TJAccountManager = class(TJavaGenericImport<JAccountManagerClass, JAccountManager>) end;

I've add some of the classes from AccountManager.pas, the JAccount Class does not have the name property, it is in the JAccountClass, but the code works, and I'm still getting the Access violation error.

回答1:

Since there have been some comments about issues using the code snippets I thought it might be helpful to throw in a full unit (albeit containing minimal import definitions) to try and belay the problems and confusion.

Here is a helper unit that works in Delphi XE8 through to Delphi 10.1 Berlin (I can't check earlier versions, but in principle it should be OK):

unit AccountEmailsU;

interface

function GetAccountEmails(const AccountType: String): TArray<String>;

implementation

uses
  Androidapi.Helpers,
  Androidapi.Jni,
{$IF Declared(RTLVersion) and (RTLVersion >= 31)}
  // Delphi 10.1 Berlin adds in full imports for the accounts classes
  Androidapi.JNI.Accounts;
{$ELSE}
  Androidapi.JNIBridge,
  Androidapi.JNI.App,
  Androidapi.JNI.GraphicsContentViewText,
  Androidapi.JNI.JavaTypes,
  Androidapi.JNI.Os;

type
// ===== Forward declarations =====

  JAccount = interface;//android.accounts.Account
  JAccountManager = interface;//android.accounts.AccountManager

// ===== Interface declarations =====

  JAccountClass = interface(JObjectClass)
    ['{94EE6861-F326-489F-8919-E20B39E3D9C1}']
  end;

  [JavaSignature('android/accounts/Account')]
  JAccount = interface(JObject)
    ['{71476381-8B6E-471F-9189-9857ECD7508C}']
    function _Getname: JString; cdecl;
    function _Gettype: JString; cdecl;
    property name: JString read _Getname;
    property &type: JString read _Gettype;
  end;
  TJAccount = class(TJavaGenericImport<JAccountClass, JAccount>) end;

  JAccountManagerClass = interface(JObjectClass)
    ['{96273844-2D84-47F0-BFD5-14B73402F843}']
    {class} function &get(context: JContext): JAccountManager; cdecl;
  end;

  [JavaSignature('android/accounts/AccountManager')]
  JAccountManager = interface(JObject)
    ['{9FA4077B-4628-433C-BAFC-9EB299DA9C98}']
    function getAccountsByType(type_: JString): TJavaObjectArray<JAccount>; cdecl;
  end;
  TJAccountManager = class(TJavaGenericImport<JAccountManagerClass, JAccountManager>) end;
{$ENDIF}

function GetAccountEmails(const AccountType: String): TArray<String>;
var
  AccountManager: JAccountManager;
  Accounts: TJavaObjectArray<JAccount>;
  Account: JAccount;
  AccountLoopCounter: Integer;
begin
{$IF RTLVersion >= 30}
  AccountManager := TJAccountManager.JavaClass.get(TAndroidHelper.Context);
{$ELSE}
  AccountManager := TJAccountManager.JavaClass.get(SharedActivityContext);
{$ENDIF}
  if AccountManager <> nil then
  begin
    Accounts := AccountManager.getAccountsByType(StringToJString(AccountType));
    if Accounts <> nil then
    begin
      SetLength(Result, Accounts.Length);
      for AccountLoopCounter := 0 to Pred(Accounts.Length) do
      begin
        //Account := Accounts.Items[AccountLoopCounter];
        Account := TJAccount.Wrap(Accounts.GetRawItem(AccountLoopCounter));
        Result[AccountLoopCounter] := JStringtoString(Account.name);
      end
    end;
  end;
end;

procedure RegisterTypes;
begin
  TRegTypes.RegisterType('AccountEmailsU.JAccount', TypeInfo(AccountEmailsU.JAccount));
  TRegTypes.RegisterType('AccountEmailsU.JAccountManager', TypeInfo(AccountEmailsU.JAccountManager));
end;

initialization
  RegisterTypes;
end.

This can be used in a fashion akin to this:

uses
{$IF RTLVersion >= 31}
  FMX.DialogService,
//{$ELSE}
//  FMX.Dialogs,
{$ENDIF}
  AccountEmailsU,
  MiscU;

procedure TForm1.btnGetAccountEmailsClick(Sender: TObject);
const
  AccountType = 'com.google';
var
  AccountNames: TArray<String>;
  AccountLoopCounter: Integer;
begin
  if not HasPermission('android.permission.GET_ACCOUNTS') then
{$IF RTLVersion >= 31}
    TDialogService.MessageDialog('App does not have the GET_ACCOUNTS permission',
      TMsgDlgType.mtError, [TMsgDlgBtn.mbCancel], TMsgDlgBtn.mbCancel, 0, nil)
{$ELSE}
    MessageDlg('App does not have the GET_ACCOUNTS permission',
      TMsgDlgType.mtError, [TMsgDlgBtn.mbCancel], 0)
{$ENDIF}
  else
  begin
    AccountNames := GetAccountEmails(AccountType);
    AccountsListBox.Items.Clear;
    for AccountLoopCounter := Low(AccountNames) to High(AccountNames) do
      AccountsListBox.Items.Add(AccountNames[AccountLoopCounter])
  end;
end;

The permissions checking code comes from this helper unit:

unit MiscU;

interface

function HasPermission(const Permission: string): Boolean;

implementation

uses
  FMX.Helpers.Android,
  Androidapi.Helpers,
  Androidapi.JNI.JavaTypes,
  Androidapi.JNI.GraphicsContentViewText;

function HasPermission(const Permission: string): Boolean;
begin
  //Permissions listed at http://d.android.com/reference/android/Manifest.permission.html
{$IF RTLVersion >= 30}
  Result := TAndroidHelper.Context.checkCallingOrSelfPermission(
{$ELSE}
  Result := SharedActivityContext.checkCallingOrSelfPermission(
{$ENDIF}
    StringToJString(Permission)) =
    TJPackageManager.JavaClass.PERMISSION_GRANTED
end;

end.


回答2:

You should use the name property of the Account and not asking to convert the object to a string.

mmLog.Lines.Add(jstringtostring( jAcc.name));


回答3:

getAccountsByType() returns an array of Account objects, not an array of class types. And check for nil pointers.

Try this instead:

var
  jAm: JAccountManager;
  accounts: TJavaObjectArray<JAccount>;
  jAcc: JAccount;
begin
  jAM := TJAccountManager.JavaClass.get(SharedActivityContext);
  if jAM <> nil then begin
    accounts := TJavaObjectArray<JAccount>.Wrap(jAM.getAccountsByType(StringToJString('com.google')));
    if accounts <> nil then begin
      mmLog.Lines.Add('Length Accounts: ' + IntToStr(accounts.Length));
      if accounts.Length > 0 then begin
        jAcc := accounts.Items[0];
        mmLog.Lines.Add(JStringtoString(jAcc.name));
      end else begin
        mmLog.Lines.Add('no accounts available');
      end;
    end;
  end else begin
    mmLog.Lines.Add('no accounts found');
  end;
else begin
  mmLog.Lines.Add('no account manager available');
end;


回答4:

copy {class} function _Getname: JString; cdecl; to JAccount class, then use this code:

var
  jAm: JAccountManager;
  accounts: TJavaObjectArray<JAccount>;
  jAcc: JAccount;
begin
  jAM := TJAccountManager.JavaClass.get(SharedActivityContext);
  if jAM <> nil then begin
    accounts := TJavaObjectArray<JAccount>.Wrap(jAM.getAccountsByType(StringToJString('com.google')));
    if accounts <> nil then begin
      mmLog.Lines.Add('Length Accounts: ' + IntToStr(accounts.Length));
      if accounts.Length > 0 then begin
        jAcc := accounts.Items[0];
        mmLog.Lines.Add(JStringtoString(jAcc._Getname));
      end else begin
        mmLog.Lines.Add('no accounts available');
      end;
    end;
  end else begin
    mmLog.Lines.Add('no accounts found');
  end;
end;