SQLの窓

2012年11月28日


Android(4.0.3) : WebView を使って Google にログインしてアクセストークンを取得する

ログイン画面は Google が提供する画面が表示されます。



そこでログインすると、登録済のアプリケーションの名前が表示されて、処理を許可するかどうかの確認画面が表示されて、許可すると登録済のリダイレクト URL が表示されます。ここで使用している Google API に登録したアプリケーションは、『Client ID for web applications』として、自分の API Console で作成や表示が可能です。

※ Calendar API と Google+ API を ON にしています。

クリップボードアクセスについて

エミュレータでログインする場合、ユーザーの入力を省略する為に、アプリケーションでユーザ文字列をクリップボードにセットしています。その際、ClipboardManager を使うわけですが、android.content.ClipboardManager を使っています。もうひとつ、android.text.ClipboardManager がありますが、これは将来的に廃止するので android.content.ClipboardManager を使うようにと指示されます。これは、Eclipse でのチェック機能でかなり明示されますし、android.content.ClipboardManager を使っても @SuppressLint("NewApi") を記述するようにガイドされました。

WebView の JavaScript 設定

Google 側の処理で、JavaScript が必要になります。もし、JavaScript が使用できない場合はメッセージが表示されて先へ進まなくなりますので、webView.getSettings().setJavaScriptEnabled(true); を実行してからログイン画面を表示しています。

アクセストークンを POST で取得

WevView のアドレスに『仮のアクセストークン』が設定されるので、そこから取り出して正式なアクセストークンを取得する為に『Client secret』をさらにセットして呼び出します。その結果が JSON なので、その中の access_token を GSON で取得するので、プロジェクトの lib フォルダには、GSON の jar を保存しておく必要があります。

Web に対するアクセスの為の AsyncTask

Android 4.0.3 でインターネットにアクセスしようとすると、たとえ SDK のメソッドであっても同期処理の場合は非同期処理として記述しなおす必要があります。その際のむ最も簡単な方法として、無名の AsyncTask のインスタンスを使用しています。

UI 処理は onPostExecute で

doInBackground は、別スレッドなので画面にアクセスはできません。UI スレッドである onPostExecute に必要なデータを return して処理する必要があります。( ジェネリック型の型パラメータの最後のパラメータの型で受け渡しの型を決定します )

※ 『ジェネリック型の型パラメータ』は Microsoft の C# の表現です / <String, Void, String>

※ 最初の型パラメータは doInBackground に対するもので、配列になります
※ 真ん中のパラメータは、doInBackground から publishProgress で投げた内容を配列で受け取ります
※ ここでは使用していないので Void にしています。

AsyncTask | Android Developers
// ****************************************************************
// アクティビティの初期処理
// ****************************************************************
@SuppressLint("NewApi")
@Override
public void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);

	// ************************************************************
	// WebView のイベントを処理する
	// ************************************************************
	WebView webView = (WebView)findViewById(R.id.webView1);
	webView.setWebViewClient( new WebViewClient() {
			// ページがユーザに対して表示された時のイベント
			public void onPageFinished (WebView view, String url) {
				Log.i("onPageFinished:", url);
			}
			
			public boolean shouldOverrideUrlLoading(WebView view, String url) {
				Log.i("shouldOverrideUrlLoading:", url);
				if (url.indexOf("https://plus.google.com/u/0/101280392108947207617/posts") == 0) {
					int cur = url.indexOf("=");
					String code = url.substring(cur + 1);
					Log.i("onPageFinished:", code);

					(new AsyncTask<String, Void, String>() {
						@Override
						protected String doInBackground(String... args) {
							String json_string = "";

							try {
								URL url = new URL("https://accounts.google.com/o/oauth2/token");
								// 接続オブジェクト
								HttpURLConnection http = (HttpURLConnection)url.openConnection();
								http.setConnectTimeout(30000);
								http.setReadTimeout(30000);
								http.setDoInput(true);
								http.setRequestMethod("POST");

								// ****************************************************
								// 送信部分
								// ****************************************************
								OutputStreamWriter osw =
									new OutputStreamWriter(http.getOutputStream());
								BufferedWriter bw = new BufferedWriter(osw);
								
								// 送信データ部分
								String client_id = "";
								String client_secret = "";
								String redirect_uri = "https://plus.google.com/u/0/101280392108947207617/posts";
								
								String body = "";
								body = "code=" + args[0];
								body += "&grant_type=authorization_code";
								body += "&redirect_uri=" + redirect_uri;
								body += "&client_id=" + client_id;
								body += "&client_secret=" + client_secret;
								bw.write( body );
		
								bw.close();
								osw.close();
										
								// ****************************************************
								// 受信部分
								// ****************************************************
								// UTF-8 でリーダーを作成
								InputStreamReader isr = new InputStreamReader(http.getInputStream(), "UTF-8");
								
								// 行単位で読み込む為の準備   
								BufferedReader br = new BufferedReader(isr);   
								String line_buffer;   
								// BufferedReader は、readLine が null を返すと読み込み終了   
								while ( null != (line_buffer = br.readLine() ) ) {   
									// コマンドプロンプトに表示   
									json_string += line_buffer;
								}
					 
								br.close();
								isr.close();
		
								http.disconnect();
							}
							catch (Exception e) {
								e.printStackTrace();
							}
							
							// アクセストークン
							return json_string;
						}
						@Override
						protected void onPostExecute(String result) {

							// パーサーを取得
							JsonParser jp = new JsonParser();
							// 文字列をパース
							JsonElement je = jp.parse(result);

							// key と value を取得する為に、JsonObject から、entrySet メソッドを実行
							Set<Map.Entry<String, JsonElement>> entrySet = je.getAsJsonObject().entrySet();

							// イテレータを取得
							Iterator<Map.Entry<String, JsonElement>> it = entrySet.iterator();

							// 一覧表示
							while(it.hasNext())
							{
								Map.Entry<String, JsonElement> entry = it.next();
								
								String key = entry.getKey();
								JsonElement value = entry.getValue();
								
								if ( key.equals("access_token") ) {
									EditText editText = (EditText)findViewById(R.id.editText1);
									editText.setText(value.getAsString());
									break;
								}
							}			
						}
					}).execute(code);
					
				}
				// true を指定すると、リダイレクト処理をしなくなる
				// false でリダイレクトの場合、ここが二度よばれてから onPageFinished が呼ばれる
			    return false;
			}
		}
	);		

	String client_id = "";
	String loginUrl = "https://accounts.google.com/o/oauth2/auth";
	String type = "code";
	String redirect_uri = "https://plus.google.com/u/0/101280392108947207617/posts";
	String scope = "https://www.googleapis.com/auth/calendar+https://www.googleapis.com/auth/calendar.readonly+https://www.googleapis.com/auth/plus.me";

	String access_url = "";

	access_url = loginUrl;
	access_url += "?response_type=" + type;
	access_url += "&client_id=" + client_id;
	access_url += "&redirect_uri=" + redirect_uri;
	access_url += "&scope=" + scope;

	// ログイン画面
	webView.getSettings().setJavaScriptEnabled(true);
	webView.loadUrl(access_url);
	
	ClipboardManager clipboardManager = (ClipboardManager)this.getSystemService(CLIPBOARD_SERVICE);
	
	//クリップボードに格納するItemを作成
	ClipData.Item item = new ClipData.Item("ユーザID");

	// データの種類
	String[] mimeType = new String[1];
	mimeType[0] = ClipDescription.MIMETYPE_TEXT_PLAIN;
	 
	//クリップボードに格納するClipDataオブジェクトの作成
	ClipData cd = new ClipData(new ClipDescription("text_data", mimeType), item);
	
	clipboardManager.setPrimaryClip(cd);

}


タグ:android java
posted by lightbox at 2012-11-28 17:14 | Android | このブログの読者になる | 更新情報をチェックする

2012年11月21日


Eclipse+Android(4.0.3) のメニューの処理

※ Android の最新(2012年6月末)の開発環境に関して

SkyDrive へ移動


普通にプロジェクトを作成すると以下のようになります。メニューの XML は最初から作成されていて、メニューが作成される最初のイベント(onCreateOptionsMenu)も作成されていますので、onOptionsItemSelected を追加して、メニューの処理を実装します。

▼ ifRoom フラグの意味
それのための余地がある場合にのみ、アクションバーでこの項目を配置します。



※ この設定により、以降のメニュー位置になります


Google、AndroidのMenuボタンに“告別
Googleは既存アプリのMenuボタンが担っていた機能を、「ActionBar」クラスを使って組み込み直すよう勧めている。
package com.example.androidapp;

import android.os.Bundle;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
    
    @Override
	public boolean onOptionsItemSelected(MenuItem item) {
		
		// 選択されたメニュー項目の ID
		int menuId = item.getItemId();
		// 選択されたメニュー項目に表示されているテキスト
		String menuText = item.getTitle().toString();
		// 画面上のテキストフィールド
		EditText editText = (EditText)findViewById(R.id.editText1);
		// 画面上画像表示用コントロール
		ImageView imageView = (ImageView)findViewById(R.id.imageView1);
		
		switch(menuId){
		case R.id.menu_action1:
			Log.i("DEBUG:", "処理1が選択されました");
			// 実際の番号でアイコンリソースを指定
			imageView.setImageDrawable(getResources().getDrawable(17301555));
			
			break;
		case R.id.menu_action2:
			Log.i("DEBUG:", "処理2が選択されました");
			// 定義IDでアイコンリソースを指定
			imageView.setImageDrawable(getResources().getDrawable(android.R.drawable.ic_menu_add));
			break;
			
		case R.id.menu_post:
			// 画面遷移用
			Log.i("DEBUG:", "投稿画面へ移動します");
			break;
		}

		// 選択されたメニュー項目に表示されているテキストを
		// テキストフィールドに表示
		editText.setText(menuText);

		return true;
	}	
}

ここでは、『投稿』で画面遷移を想定しています。17301555 は、android.R.drawable.ic_menu_add実際の値です。



画面定義
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:padding="20px"
        android:text="CTRL+F12でエミュレータを横にすると、メニューにテキストが付加されます" />

    <EditText
        android:id="@+id/editText1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentRight="true"
        android:layout_below="@+id/textView1"
        android:ems="10" />

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="48px"
        android:layout_height="48px"
        android:layout_below="@+id/editText1"
        android:layout_marginTop="24dp"
        android:src="@drawable/ic_action_search" />

</RelativeLayout>



メニュー項目定義
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_post"
          android:icon="@android:drawable/ic_menu_save"
          android:title="投稿"
          android:showAsAction="ifRoom|withText" />
    <item android:id="@+id/menu_action1"
        android:title="処理1"
        android:orderInCategory="100"
        android:showAsAction="never" />
    <item android:id="@+id/menu_action2"
        android:title="処理2"
        android:orderInCategory="100"
        android:showAsAction="never" />
</menu>

android:drawable の android は、package を意味しています。ic_menu_save 以外もたくさん定義されています。

関連する Android ドキュメント

Action Bar

関連する記事

android-binding を使って Windows C#(XAML) のようなバインド処理の実装



posted by lightbox at 2012-11-21 23:48 | Android | このブログの読者になる | 更新情報をチェックする

Google 拡張の『Screen Capture』のツールバーからのポップアップメニューが表示されないバグの対処方法とついでに日本語化する方法。



上のような状態になって、表示されているキャプチャされた画像の下にポップアップメニューが隠れてしまいます。(v5.0.5)

原因は、z-index が指定されていないだけなので、まず、以下のフォルダにアクセスします。
(ユーザ名はログインしているユーザ名です)
C:\Users\ユーザ名\AppData\Local\Google\Chrome\User Data\Default\Extensions\cpngackimfmofbokmjmljamhdncknpmg\5.0.5_0
(※ これは Windows 7 です)

そして、showimage.css を開いて .toolbar に z-index: 10000; のように大きな値を設定して、ツールバーが手前になるようにします。
.toolbar {
  position:fixed;
  height: 30px;
  margin-left: 10px;
  padding-top: 6px;
  z-index: 10000;
}

保存後はいったん Google Chrome を終了させれば正しく動くようになります。そこでついでに『日本語化』します。これは、messages.json というファイルなので以下からダウンロードして下さい。

SkyDrive へ移動


このファイルを _locales フォルダの中に ja フォルダを作成して その中にコピーして下さい。その後はやはり Google Chrome をいったん終了する必要があります。







posted by lightbox at 2012-11-21 16:15 | Google Chrome | このブログの読者になる | 更新情報をチェックする

2012年11月19日


Windows8(C#)からFacebook へ画像をアップロードする。

▼ 実行画面

ログイン > 画像表示 > 画像投稿
( WEB カメラの画像も投稿できます )

SkyDrive へ移動


実行するには、Facebook での APIキーが必要になります。MainPage.xaml.cs の先頭に 『public String apiKey = "";』とありますので変更して下さい。
先にログインしてアクセストークンを取得する必要がありますが、サンプルコードに実装してあります。この処理で取得するのは2時間使用可能なアクセストークンになります。60日間に変更するには、手動でも可能なのでこちらを参考にして下さい(一旦60日に変更すると、この処理で取得しても60日になるようです)。

ファイルのアップロードは、通常の仕様通りですが、具体的なフォーマットをソースコードで知りたい場合は、『Windows Phone から Facebook に画像アップロード』を参照するか、実行時にローカルのWEBサーバーに対して実行してダンプしてみて下さい。AN HTTPD ならば、トレースログを有効にすると見る事ができます。

ファイルのアップロードには、画像データのバイト配列が必要になりますが、その読み込み部分もサンプルに実装されています。具体的には、StorageFile から IBuffer を作成して、 CryptographicBuffer.CopyToByteArray メソッドを実行するのが最も簡単です( 他にも方法はあります )

HTTP の仕様(マルチパートを使用する方法)に合わせてデータを作る方法としては、System.Net.Http(HttpClient の名前空間)に全て用意されていました。仕様をご存じな方であれば非常に簡単に実装できる事がお解りになると思います。

これらの処理は全て本来非同期ですが、Windows8 の非同期処理の実装方法によってイベント等を記述する必要がなくなっています。この実装の概要に関しては、『Windows8 の非同期処理(C#)』を参照して下さい。
try
{
	HttpClient httpClient = new HttpClient();
	httpClient.MaxResponseContentBufferSize = int.MaxValue;
	httpClient.DefaultRequestHeaders.ExpectContinue = false;

	MultipartFormDataContent form = new MultipartFormDataContent();
	StringContent sc = new StringContent(PostBody.Text);
	sc.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("post-data");
	sc.Headers.ContentDisposition.Name = "message";
	form.Add(sc, "message");

	var fileContent = new System.Net.Http.ByteArrayContent(data);
	fileContent.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data");
	fileContent.Headers.ContentDisposition.FileName = "imagefile.jpg";
	fileContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
	fileContent.Headers.ContentDisposition.Name = "source";
	form.Add(fileContent, "source");

	HttpResponseMessage response = await httpClient.PostAsync(api_base_url+"me/photos?access_token=" + accessToken, form);
	String text = await response.Content.ReadAsStringAsync();

	Debug.WriteLine(text);

	MessageDialog msg = new MessageDialog("投稿が終了しました");

	msg.Commands.Add(new UICommand(
		"確認",
		new UICommandInvokedHandler(this.CommandInvokedHandler)));

	// Set the command that will be invoked by default
	msg.DefaultCommandIndex = 0;

	// Set the command to be invoked when escape is pressed
	msg.CancelCommandIndex = 1;

	// ダイアログ表示
	await msg.ShowAsync();

}
catch (Exception Err)
{
	throw;
}

参考

Uploading a file with Windows 8 WinRT -
How-To: Use the Graph API to Upload Photos to a user’s profile - Facebook開発者

関連する記事

Windows Phone から Facebook に画像アップロード
HTML(ローカル) を使用して Facebook へ画像をアップロードする
手動で Facebook API の 60日間の アクセストークンを取得する



タグ:Windows8 C# Facebook
posted by lightbox at 2012-11-19 19:16 | Win8 ストアアプリ | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します
container 終わり



フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

CSS ドロップシャドウの参考デモ
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり