ともちゃんのアプリ開発日記

組込みC言語プログラマだったともちゃんが、四苦八苦しながら、AndroidのJAVA/Kotlin、iOSのSwiftUIを習得して行きます。ともちゃんの備忘録も兼ねています。

OAuth2認証を使用して、添付ファイル付きGmailを自動送信する

OAuth2認証を使用して、添付ファイル付きGmailを自動送信しようとしたとき、かなりつまづいたので、記事にします。

1.Javamail-androidのダウンロード

activation.jar、additionnal.jar、mail.jarをダウンロードし、libsフォルダに入れます。

2.build.gradle (app)

dependencies {
implementation 'com.google.api-client:google-api-client:1.25.0'
implementation 'com.google.oauth-client:google-oauth-client-jetty:1.23.0'
implementation 'com.google.apis:google-api-services-gmail:v1-rev83-1.23.0'
}

3.uses-permissions

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />

4.OAuth2 AuthTokenの取得

private fun authenticate() {
val manager = AccountManager.get(this@AuthenticationActivity)
val prefs3 = getSharedPreferences("setting3", MODE_PRIVATE)
val editor3 = prefs3.edit()
val previousAuthToken = prefs3.getString("AuthToken", null)
if (previousAuthToken != null) {
manager.invalidateAuthToken("com.google", previousAuthToken)
}
manager.getAuthToken(
Account(mailAddressFrom, "com.google"),
"oauth2:https://www.googleapis.com/auth/gmail.send",
null,
false,
{ future ->
try {
val bundle = future.result
val intent = bundle[AccountManager.KEY_INTENT] as Intent?
// 認証されていない場合は認証許可ダイアログを表示
if (intent != null) {
startActivityForResult.launch(intent)
} else {
val authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN)
editor3.putString("AuthToken", authToken)
editor3.apply()
val prefs = getSharedPreferences("Startup", MODE_PRIVATE)
val editor = prefs.edit()
editor.putString("Authenticate", "authenticate_001")
editor.apply()
finish()
}
} catch (e: OperationCanceledException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
} catch (e: AuthenticatorException) {
e.printStackTrace()
}
},
null
)
}

5.Gmailの送信ルーチン

public class GmailSendHelper {

public GmailSendHelper() {
}

public static MimeMessage createEmailWithAttachment(
InternetAddress[] internetAddressesTo,
String from,
String subject,
String textBody,
File file
) throws MessagingException {
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);

MimeMessage email = new MimeMessage(session);

email.setFrom(new InternetAddress(from));
for (InternetAddress internetAddress : internetAddressesTo) {
email.addRecipient(javax.mail.Message.RecipientType.TO, internetAddress);
}
email.setSubject(subject);

MimeBodyPart mimeBodyPart = new MimeBodyPart();
mimeBodyPart.setText(textBody, "utf-8");

Multipart multipart = new MimeMultipart();
multipart.addBodyPart(mimeBodyPart);

mimeBodyPart = new MimeBodyPart();
FileDataSource source = new FileDataSource(file);

mimeBodyPart.setDataHandler(new DataHandler(source));
mimeBodyPart.setFileName(file.getName());

multipart.addBodyPart(mimeBodyPart);
email.setContent(multipart);

return email;
}

public static Message createMessageWithEmail(MimeMessage emailContent)
throws MessagingException, IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
emailContent.writeTo(buffer);
byte[] bytes = buffer.toByteArray();
String encodedEmail = Base64.encodeBase64URLSafeString(bytes);
Message message = new Message();
message.setRaw(encodedEmail);
return message;
}

public static Message sendMessage(
Gmail service,
String userId,
MimeMessage emailContent
) throws MessagingException, IOException {
Message message = createMessageWithEmail(emailContent);
message = service.users().messages().send(userId, message).execute();

return message;
}
}
 

6.Gmail送信ルーチンの呼び出し

AccountManager manager = AccountManager.get(this);
manager.getAuthToken(new Account(mailAddressFrom, "com.google"),
"oauth2:https://www.googleapis.com/auth/gmail.send",
null,
false,
new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> future) {
try {
bundle = future.getResult();
AsyncSendEmail asyncSendEmail = new AsyncSendEmail();
asyncSendEmail.execute();
} catch (OperationCanceledException | IOException | AuthenticatorException e) {
e.printStackTrace();
}
}
}, null);
private static class AsyncSendEmail {

private class AsyncRunnable implements Runnable {

Handler handler = new Handler(Looper.getMainLooper());
@Override
public void run() {
// ここにバックグラウンド処理を書く
try {
String authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);

final NetHttpTransport HTTP_TRANSPORT = new com.google.api.client.http.javanet.NetHttpTransport();

Gmail service = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(authToken))
.setApplicationName(APPLICATION_NAME)
.build();

assert resultOfExport.filePath != null;
GmailSendHelper.sendMessage(
service,
mailAddressFrom,
GmailSendHelper.createEmailWithAttachment(
internetAddressesTo,
mailAddressFrom,
resultOfExport.mailSubject,
resultOfExport.mailText,
new File(resultOfExport.filePath)
)
);
} catch (MessagingException | IOException e) {
e.printStackTrace();
}

handler.post(new Runnable() {
@Override
public void run() {
onPostExecute();
}
});
}
}

void onPreExecute() {
// ここに前処理を記述します
// 例) プログレスダイアログ表示
transmissionStatus = DURING_TRANSMISSION;
}

void execute() {

onPreExecute();
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.submit(new AsyncRunnable());
}

void onPostExecute() {
// バックグランド処理終了後の処理をここに記述します
// 例) プログレスダイアログ終了
transmissionStatus = END_OF_TRANSMISSION;
}
}

7.API ConsoleでクライアントIDを登録する。

https://console.developers.google.com/apis/credentials

で認証情報を獲得します。「認証情報を作成」→「ウィザードで選択」を選択します。

f:id:uchida001tmhr:20180630072638p:plain

f:id:uchida001tmhr:20180630072829p:plain

名前:適当な名前を付けます。

名証明書フィンガープリント(デバッグ時:Windows):

keytool -exportcert -alias androiddebugkey -keystore "%USERPROFILE%\.android\debug.keystore" -list -v

名証明書フィンガープリント(リリース時:Windows):

keytool -exportcert -keystore [jksファイルのフルパス] -list -v

 上記を実行:

f:id:uchida001tmhr:20180630073631p:plain

パスワード(デバッグ時):android

パスワード(リリース時):jksファイルに設定したパスワード

 

SHA1のフィンガープリントをコピーして、上記「OAuthクライアントIDの作成」に貼り付ける。

(2018/10/21追記) Google Play アプリ署名を有効にして、Android App Bundleとしてリリースする場合、名証明書のフィンガープリントには、Google Developer Consoleの「アプリのリリース」→「アプリの署名」の「アプリへの署名証明書」のSHA1フィンガープリントを持ってくる必要があります。keytoolで出力したフィンガープリントではうまくいきませんので、ご注意を!!

 

これで、コンパイルデバッグすれば、自動でOAuth2認証を行い、添付メール付きメールを自動送信してくれます。