🐕

【過去Blogからの移行記事】Webkitを使ってGoogle Calendarからエクスポートしたデータを取得する

2022/09/18に公開

表題の件について、拙作のアプリ「Gcal Importer」での実装をかいつまんで書き出してみたいと思います。

対象source: Gcal_Importer - src/net/crappo/android/androics/BrowserForDownLoadActivity.java

【このアクティビティの目的】

Google Calendarのエクスポート機能にURLを指定してアクセスし、iCalendar形式のデータ(以後ICSデータと呼ぶ)をアプリ内領域に取得すること。

上記の目的を達成する手段として、以下を使いました。

  • android.webkit.WebView
  • アクセス先を制御するためにWebViewにloadさせるHTML(assetsに配置)
  • android.webkit.WebViewClient をExtendsしたカスタムクラス
  • HttpClientを使ってCalendarのエクスポートURLにアクセスするDownloadListener

Androidでは個々のアプリに対してWebブラウザ機能を部品として提供できるようで、アプリ側ではActivityのContentViewにこの部品(WebViewオブジェクト)をaddすることで使います。

例を挙げるとこんな感じ。(もちろんXMLのレイアウトファイル作成とRelativeLayoutのid設定は予めやっておく。)

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        RelativeLayout rl = (RelativeLayout)findViewById(R.id.test_relative_top);

        WebView webView = new WebView(this);
        webView.setVerticalScrollbarOverlay(true);
        rl.addView(webView);
        webView.loadUrl("http://www.google.co.jp/");
    }

実際のコード中では、上記に加えてJavascritpの有効化や画像オートロードの有効化などを行ってますが、そこはどこかからのコピp(ry

こうして組み込んだWebViewに対してloadUrl()メソッドで目的のURLを読み込ませるわけですが、本アプリでは取り扱うURLを限定したいがために、後述する処理を追加しています。

assetsの利用とカスタムWebViewClient

本アプリでは、assetsに配置したHTMLファイルを使います。

とは言え、使い方としてはWebViewオブジェクトでloadUrlするだけなので、AssetManagerを使ったりはせずに、"file:///android_asset/redirect.html"といったURL文字列でアクセスします。

assetsフォルダに配置してあるHTMLファイルは3点で、次のような働きをします。

  • redirect.html - WebViewに最初に読み込まれるページで、Googleカレンダーのエクスポート処理URLへリダイレクトします。
  • downloading.html - Googleカレンダーからのエクスポートが実行されダウンロードが始まった時に表示されるページで、特に処理は行いません。
  • announce.html - 本アプリの目的地となるべきURL以外へのアクセスをしようとした場合に表示されるページで、特に処理は行いません。

redirect.htmlではHTMLヘッダ部分のMETAタグで下記のようにしてリダイレクトします。

    *<!-- HTMLのHEADER部分に書いた下記のメタタグでリダイレクト -->*

    <META HTTP-EQUIV="Refresh" CONTENT="0; URL=https://www.google.com/calendar/exporticalzip" />

redirect.htmlによってGoogleカレンダーのエクスポート処理URLへアクセスが行われると、google側のシステムで認証処理が差し込まれます。この認証処理の最中にユーザが認証以外のページへ行ったりしないように、カスタムのWebViewClientを用意してshouldOverrideUrlLoading()メソッド内でアクセス先を判定します。

以下が判定処理の部分です。

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        urlParse(url);

        if ( hostName.equals("accounts.google.com") && pathName.equals("/SignUp")) {

            *// ユーザが余計な画面遷移(アカウントの作成)を行った時にはToastで「お断り」表示。*
            view.loadUrl(
"file:///android_asset/redirect.html");

            Toast.makeText(activityObj, R.string.browser_toast, Toast.LENGTH_LONG).show();

             return false;

        } else if ( hostName.equals("www.google.com") && pathName.equals("/accounts/recovery")) {

            *// ユーザが余計な画面遷移(パスワードを忘れた場合のrecovery画面)を行った時には「お断り」をする。*
            webView.loadUrl("file:///android_asset/announce.html");
            return false;
        }
        view.loadUrl(url);
        return false;
    }

DownloadListenerの実装

本アプリでは、ICSデータダウンロードの際にGoogleの認証処理をユーザに行ってもらう必要があるため、ページ遷移時にはcookieを使って認証情報を引き継いでいかなければなりません。

そこでWebViewによるネットからのファイルダウンロードですが、私が選んだ実装方法は下記です。

  • カスタムWebViewClientでcookieを保存する。
  • DownloadListenerを実装してWebViewにsetDownloadListener()メソッドでセットする。
  • 事前に保存したcookieからDownloadListenerのonDownloadStart()メソッドで認証情報を取り出し、そのcookieをHttpGetオブジェクトのsetHeader()メソッドでHTTPヘッダにセットする。
  • DefaultHttpClientオブジェクトのexecute()メソッドに、上記までで用意したHTTPヘッダを与えてレスポンスを得る。
  • ResponseHandlerの実装の中で、レスポンスを処理(ファイルシステムへ保存)するコードを書く。

cookieの保存はWebViewClientのonPageFinished()内で下記のようにして行いました。

    *// WebViewのCookieを取得*
    cookMgr = CookieManager.getInstance();
    String cookie = cookMgr.getCookie(urlStr);
    if (cookie != null) {
        String[] cookies = cookie.split(";");
        for (String keyValue : cookies) {
            keyValue = keyValue.trim();
            int index = keyValue.indexOf("=");

            String[] cookieSet = new String[] { keyValue.substring(0, index), keyValue.substring(index + 1) };

            *// Cookieを作成*

            BasicClientCookie bCookie = new BasicClientCookie(cookieSet[0], cookieSet[1]);

            bCookie.setDomain(urlObj.getHost());
            bCookie.setPath("/");
            *// CookieStoreを取得*
            DefaultHttpClient defHttpClient;
            if (client != null)    defHttpClient = (DefaultHttpClient)client;
            else                   defHttpClient = new DefaultHttpClient();
            cookStore = defHttpClient.getCookieStore();
            *// Cookieを追加*
            cookStore.addCookie(bCookie);
        }
    }

そうして保存したcookieをDownloadListenerのonDownloadStart()メソッド内で下記のようにして、DefaultHttpClientオブジェクトに渡してレスポンスを得ます。

    String cookStr = "";
    cookMgr = CookieManager.getInstance();
    cookStr = cookMgr.getCookie(url);

    httpGet = new HttpGet(url);
    httpGet.setHeader("Cookie", cookStr);
    client = new DefaultHttpClient();
    try {
        client.execute(httpGet, new ResponseHandler(){
            @Override

            public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {

                switch (response.getStatusLine().getStatusCode()) {
                case HttpStatus.SC_OK:
                    *// ~~*
                    *// レスポンスに対してやりたい処理を行います。本アプリでは、*
                    *// ・ダウンロードする対象のファイル名を取り出す*
                    *// ・所定のディレクトリへ、そのファイル名で書き出す*
                    *// (詳細な処理内容は冒頭で示したGitHubの公開sourceにあります)*
                    *// ~~*
                case HttpStatus.SC_NOT_FOUND:
                    throw new RuntimeException("HTTP Status : 404 Not Found.");
                default:
                    throw new RuntimeException("HTTP Status : Error.");
                }
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        client.getConnectionManager().shutdown();
    }

本アプリではこのようにして、アプリ内ブラウザでGoogleの認証を通過してiCalendarデータを取得する処理を実装しました。

実装の説明は以上になります。誰かの何かの参考になれば幸いです。

なお、この機能はユーザにGoogleアカウントのアカウント名・パスワードの入力を求めるものです。しかし「素性の知れないアプリにアカウントの認証で必要となる情報を入力するなんてイヤだ」という方もいると思います。

そのような方向けには、PCからのファイル転送等を使ってAndroid上にコピーしたICSデータファイルを取り込むことができるように、FilePickerの機能をTopアクティビティのメニューに用意してあります。

本ブログではFilePickerについては取り上げませんが、sourceは公開していますのでもし興味があればTopActivity.javaのメソッドpickerForIcsFile()の箇所周辺をご覧ください。

Discussion