SQLの窓

2017年06月20日


Android での保存用テキストデータの扱いを okio で簡素化する

OkHttp はとても簡単にインターネットへのアクセスを提供してくれます。その Okhttp が依存しているストリームの処理が okio です。

当然 okio は一般的なテキストファイルのアクセスにも簡単に使えて便利なので、Android の内部で扱われる『位置付け』別に処理を実際に使用して整理してみました。

★ 全体のソースコード
build.gradle への記述 ▼ 2017/06/20 時点 compile 'com.squareup.okio:okio:1.13.0' Runtime Permission の対応 ▼ 以下のテンプレートを使用しています Android 6.0 : テストの為の Runtime Permission の対応を自動化するテンプレート okio を使用した2つのメソッドを用意 okio には、BufferedSource と BufferedSink というクラスがあり、それぞれ 入力と出力を担当しています。一応サンプルとして OkioTest.java を読むと雰囲気がつかめると思いますが、欲しい機能はそれほど複雑では無いので以下の2つのメソッドだけでも十分使いこなせると思います。
	// *************************
	// テキストファイル一括読込
	// "UTF-8" or "windows-31j"
	// *************************
	private String readTextAll(InputStream inputStream, String charset) throws Exception {
		String result;

		BufferedSource bufferedSource = Okio.buffer(Okio.source(inputStream));
		result = bufferedSource.readString(Charset.forName(charset));

		bufferedSource.close();
		inputStream.close();

		return result;
	}

	// *************************
	// ファイルコピー
	// *************************
	private void copy(InputStream inputStream, OutputStream outputStream) throws Exception {

		Source source = Okio.source(inputStream);
		BufferedSink bufferedSink = Okio.buffer(Okio.sink(outputStream));
		bufferedSink.writeAll(source);

		bufferedSink.close();
		source.close();

	}

assets に syain.csv ファイルを置いて他の場所にコピー(書込み)



syain.csv は、Windows の一般的な SHIFT_JIS のテキストファイル(CSV) です。このデータを パッケージの領域と 外部ストレージにコピーして、後でテキストファイルの読み込みをテストします。

 



他にバリエーションとして、okio で直接パッケージの領域にテキストファイル(test.txt)を "UTF-8" として書き込んでいます(23〜30)。
1) UTF-8 を指定して書き込み

Charset charset = Charset.forName("UTF-8");
sink.writeString("ストレージ情報\n",charset);

2) 専用メソッドで UTF-8 として書き込み

sink.writeUtf8(Manifest.permission.CAMERA + "\n");

※ sink は BufferedSink です
さらに、プリファレンスの書き込みも行います(36〜46)。
			try {

				InputStream inputStream;
				AssetManager assetManager = MainActivity.this.getResources().getAssets();

				// ****************************************
				// syain.csv を assets から プライベートエリアへコピー
				// ****************************************
				inputStream = assetManager.open("syain.csv");
				copy(inputStream, openFileOutput("syain.csv", MODE_PRIVATE));

				// ****************************************
				// syain.csv を assets から 外部ストレージへコピー
				// ****************************************
				createDir();
				inputStream = assetManager.open("syain.csv");
				String csvPath = Environment.getExternalStorageDirectory().getPath() + "/textfiletest/syain.csv";
				copy(inputStream, new FileOutputStream(csvPath));

				// ****************************************
				// プライベートなテキストファイルの作成と書き込み
				// ****************************************
				BufferedSink sink = Okio.buffer(Okio.sink(openFileOutput("test.txt", MODE_PRIVATE)));
				Charset charset = Charset.forName("UTF-8");
				sink.writeString("ストレージ情報\n",charset);
				sink.writeUtf8(String.format("%s\n", Environment.getExternalStorageDirectory().getPath()));
				sink.writeUtf8(Manifest.permission.CAMERA + "\n");
				sink.writeUtf8(Manifest.permission.WRITE_EXTERNAL_STORAGE + "\n");
				sink.writeUtf8(PackageManager.PERMISSION_GRANTED + "\n");
				sink.close();

				// ****************************************
				// プライベートな小さな情報を保存する為の本来の方法
				// private_data.xml の作成
				// ****************************************
				SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
				SharedPreferences.Editor editor = data.edit();

				// 現在の年月日・時分秒を取得する為の準備
				Calendar cal = Calendar.getInstance();
				SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");

				// データの書き込み
				editor.putString("now", sf.format(cal.getTime()));
				editor.putString("japan", "日本語");
				editor.commit();

			} catch (Exception e) {
				e.printStackTrace();
			}

外部ストレージにフォルダの作成

Runtime Permission の対象処理です

	// *************************
	// フォルダ作成
	// *************************
	private void createDir() {
		String imageDir = Environment.getExternalStorageDirectory().getPath() + "/textfiletest";
		Log.i("lightbox", imageDir );
		File file = new File(imageDir);
		// ディレクトリ初期作成
		if (!file.exists()) {
			if (file.mkdir() == false) {
				Log.i("lightbox", "ディレクトリを作成できませんでした");
				return;
			}
		}
	}

画面にファイルを読込み(1)

まずは、パッケージ内の領域から読み込んで画面に表示します( openFileInput )

※ 画面定義


			StringBuilder sb = new StringBuilder();

			InputStream inputStream;
			String text;

			Log.i("lightbox", "読み込み1");
			try {
				// ****************************************
				// プライベートなテキストファイルの読み込み
				// "UTF-8" or "windows-31j"
				// ****************************************
				inputStream = openFileInput("test.txt");
				text = readTextAll(inputStream,"UTF-8");

				sb.append(text);

				// ****************************************
				// プライベートなテキストファイルの読み込み
				// "UTF-8" or "windows-31j"
				// ****************************************
				inputStream = openFileInput("syain.csv");
				text = readTextAll(inputStream,"windows-31j");

				sb.append(text);

				EditText editText = (EditText) MainActivity.this.findViewById(R.id.editText);
				editText.setText(sb.toString());
				TextView textView = (TextView) MainActivity.this.findViewById(R.id.textView);
				textView.setText(sb.toString());

				// ****************************************
				// private_data.xml からデータの取得
				// ****************************************
				SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
				Log.i("lightbox", data.getString("now", "このデータはありません"));
				Log.i("lightbox", data.getString("japan", "このデータはありません"));

			} catch (Exception e) {
				e.printStackTrace();
			}
UTF-8 と SHIFT_JIS で保存してあるファイルの一括読み込みです。キャラクタセットに関する Oracle のドキュメントはこちらになります。

※ プリファレンスは Log.i で表示しています

画面にファイルを読込み(2)

外部ストレージより読み込みます( Runtime Permision の対象処理 )
new FileInputStream(path)

※ test.txt は、画面表示の都合で読み込んでいます
			StringBuilder sb = new StringBuilder();

			InputStream inputStream;
			BufferedSource bufferedSource;
			String text;

			Log.i("lightbox", "読み込み2");
			try {
				// ****************************************
				// プライベートなテキストファイルの読み込み
				// "UTF-8" or "windows-31j"
				// ****************************************
				inputStream = openFileInput("test.txt");
				text = readTextAll(inputStream,"UTF-8");

				sb.append(text);

				// ****************************************
				// 外部ストレージからテキストファイルの読み込み
				// "UTF-8" or "windows-31j"
				// ****************************************
				String csvPath = Environment.getExternalStorageDirectory().getPath() + "/textfiletest/syain.csv";
				inputStream = new FileInputStream(csvPath);
				text = readTextAll(inputStream,"windows-31j");

				sb.append(text);

				EditText editText = (EditText) MainActivity.this.findViewById(R.id.editText);
				editText.setText(sb.toString());
				TextView textView = (TextView) MainActivity.this.findViewById(R.id.textView);
				textView.setText(sb.toString());


			} catch (Exception e) {
				e.printStackTrace();
			}


関連する記事 : 一般的な Java のテキスト読込み

Java : テキストファイルを読み込んで正規表現で置換


posted by lightbox at 2017-06-20 21:17 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

Android 6.0 : テストの為の Runtime Permission の対応を自動化するテンプレート

Runtime Permission の対応はしないとアプリが動作しないのでせざるを得ないですが、本来の処理とは無関係なのでかなり面倒な上にソースコードそのものが第三者への説明の邪魔になります。

そこで、ほぼ定型化して処理する為のテンプレートとして MainActivity への配置例と、そのために必要なクラスを用意しました。
手順1 ▼ Runtime Permission の処理は、以下から permission_170610.zip をダウンロードして下さい。 ※ CheckMyPermission の内容 手順2 ▼ 解凍して、プロジェクトの "app\src\main\java" の下に com フォルダをコピーして下さい ▼ エクスプローラから見た状態 手順3 使用したい Permission を AndroidManifest.xml に記述します
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.lightbox.runtimepermissiontest">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_CALENDAR"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

手順4

MainActivity を以下のように記述して下さい
package com.example.lightbox.runtimepermissiontest;

import android.Manifest;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.lightbox.permission.CheckMyPermission;
import com.example.lightbox.permission.MyPermission;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

	private CheckMyPermission checkMyPermission;

	@Override
	public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
		// 全ての必要なパーミッションが許可された場合
		if ( checkMyPermission.checkPermission( requestCode,  grantResults )) {
			// onCreate で初期処理できるように、MainActivity をリスタート
			Intent intent = MainActivity.this.getIntent();
			MainActivity.this.finish();
			MainActivity.this.startActivity(intent);
		}
	}

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

		// 必要なパーミッションのリスト
		ArrayList<MyPermission> myPermission_list = new ArrayList<MyPermission>();
		// AndroidManifest.xml に記述した Permission の一覧
		myPermission_list.add(new MyPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,100));
		myPermission_list.add(new MyPermission(Manifest.permission.CAMERA,101));
		myPermission_list.add(new MyPermission(Manifest.permission.WRITE_CALENDAR,102));
		// Runtime Permission 用のクラスのインスタンス
		checkMyPermission = new CheckMyPermission(MainActivity.this,myPermission_list);
		if ( checkMyPermission.checkPermission() ) {
			// ここから通常の初期処理
			initSettings();
		}
	}

	private void initSettings() {

	}

}


手順5

実行します。すると、初回は許可ダイアログが指定した Permission と同数表示されて、onRequestPermissionsResult が実行されます。すると、Activity が再起動され、checkMyPermission.checkPermission() が true になって initSettings() を実行する事になります。

2回目以降(全て許可した場合)は onCreate で initSettings() が常に実行される事になります。(許可ダイアログを再度表示したい場合は、アプリを削除して再度実行します)

許可ダイログ

  


関連する記事

『Android 6.0 エミュレータで 複数の Runtime Permission の対応を簡潔に吸収するクラス( CheckMyPermission )』

Android Studio : Runtime Permission 等の裏方作業を MainActivity にさせて、本来の処理は継承したサブクラスで行う( カメラを呼び出して画像を保存させ、ImageView に表示する )


posted by lightbox at 2017-06-20 19:35 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

2017年06月10日


Android Studio : Runtime Permission 等の裏方作業を MainActivity にさせて、本来の処理は継承したサブクラスで行う( カメラを呼び出して画像を保存させ、ImageView に表示する )

Runtime Permission の処理は、『Android 6.0 エミュレータで 複数の Runtime Permission の対応を簡潔に吸収するクラス( CheckMyPermission )』でテストしたものをダウンロードできます。


※ 解凍して、プロジェクトの "app\src\main\java" の下に com フォルダをコピーして下さい


MainActivity

画面の表示はこちらで行います。onCreate で Runtime Permission のチェックの準備を行って、onRequestPermissionsResult での受け取り処理もここで完結します。

MainActivity を継承したサブクラスの onCreate でチェックを行い、既に許可済みであれば通常の初期処理を行います。

ここでは、カメラの呼び出しを行うので、カメラの Permission は必要ありませんが、ExternalStorage に画像ファイルを作成するので、AndroidManifest.xml への記述と Runtime Permission の許可処理が必要です。

また、カメラ呼び出しで private 変数がクリアされる事を想定して(そういう仕様である上に、カメラが保存したパスを返さない事から保存せざるを得ない)、MainActivity では onSaveInstanceState と onRestoreInstanceState で変数の保存と復帰を行っています。

package com.example.lightbox.cameracalltest;

import android.Manifest;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.lightbox.permission.CheckMyPermission;
import com.example.lightbox.permission.MyPermission;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    private String imagePath;
    private CheckMyPermission checkMyPermission;

    public String getImagePath() {
        return imagePath;
    }

    public void setImagePath(String imageUri) {
        this.imagePath = imageUri;
    }

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

        // 必要なパーミッションのリスト
        ArrayList<MyPermission> myPermission_list = new ArrayList<MyPermission>();
        myPermission_list.add(new MyPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,100));
        // Runtime Permission 用のクラスのインスタンス
        checkMyPermission = new CheckMyPermission(MainActivity.this,myPermission_list);

    }

    // 全ての必要なパーミッションが既に許可されているかどうか
     protected boolean  checkMyPermission() {
        return checkMyPermission.checkPermission();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        // 全ての必要なパーミッションが許可された場合
        if ( checkMyPermission.checkPermission( requestCode,  grantResults )) {
            // onCreate で初期処理できるように、MainActivity をリスタート
            Intent intent = getIntent();
            finish();
            startActivity(intent);
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        outState.putString("image_uri", imagePath);
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);

        imagePath = savedInstanceState.getString("image_uri");
    }
}


CameraCallActivity
package com.example.lightbox.cameracalltest;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class CameraCallActivity extends MainActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 画面表示は super 側
        Log.i("lightbox", "CameraCallActivity : onCreate");

        if ( CameraCallActivity.this.checkMyPermission() ) {
            // 通常処理
            initSettings();
        }
    }

    private void initSettings() {

        // ボタンイベントの登録
        Button button = (Button) CameraCallActivity.this.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("lightbox", "クリックされました");

                callCamera();
            }
        });

    }

    // カメラの呼び出し
    private void callCamera() {

        Intent intent = new Intent();
        intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);

        String imageDir = Environment.getExternalStorageDirectory().getPath() + "/cameratest";
        File file = new File(imageDir);
        // ディレクトリ初期作成
        if (!file.exists()) {
            if (file.mkdir() == false) {
                Log.i("lightbox", "ディレクトリを作成できませんでした");
                return;
            }
        }

        // ギャラリー用画像保存パス
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
        String imagePath = imageDir + "/" + sf.format(cal.getTime()) + ".jpg";
        Log.i("lightbox",  "imagePath : " + imagePath );
        // 画像ファイルの パス を保存
        CameraCallActivity.this.setImagePath(imagePath);
        file = new File( imagePath );

        // 保存してほしい URI を引き渡す
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));

        CameraCallActivity.this.startActivityForResult(intent, 101);

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if ( requestCode == 101 ) {
            if ( resultCode == Activity.RESULT_OK ) {

                // 画像表示
                Bitmap bitmap = BitmapFactory.decodeFile(CameraCallActivity.this.getImagePath());
                ImageView imageView = (ImageView) CameraCallActivity.this.findViewById(R.id.imageView);
                imageView.setImageBitmap(bitmap);

                // ギャラリーに反映
                MediaScannerConnection.scanFile(
                        CameraCallActivity.this,
                        new String[] { CameraCallActivity.this.getImagePath() },
                        new String[] { "image/jpeg" },
                        null);
            }
        }
    }
}


AndroidManifest.xml

実行するアクティビティは、CameraCallActivity なので、修正が必要です( 12行目 )
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.lightbox.cameracalltest">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".CameraCallActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>



画面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.lightbox.cameracalltest.MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:srcCompat="@mipmap/ic_launcher" />
</LinearLayout>




posted by lightbox at 2017-06-10 15:08 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

2017年06月06日


Android 6.0 エミュレータで 複数の Runtime Permission の対応を簡潔に吸収するクラス( CheckMyPermission )

商品作る場合では無く、テストで面倒なデバイス側での権限の付与の実装を避けたい場合のクラスを作ってみました。getRequestCount() のチェックをしなければ、権限を付与するまでループするようにする事もできます(許可しない場合の UI テスト等にも使えるかもしれません)

CheckMyPermission クラス
package com.example.lightbox.cameratest;

import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;

import java.util.ArrayList;

public class CheckMyPermission {

    private ArrayList<MyPermission> myPermission_list;
    private MainActivity context;

    public CheckMyPermission(MainActivity context, ArrayList<MyPermission> myPermission_list) {
        this.myPermission_list = myPermission_list;
        this.context = context;
    }

    public boolean checkPermission( int requestCode, int[] grantResults ) {
        boolean result = false;
        for( MyPermission myPermission :  myPermission_list) {
            if ( myPermission.getPermissionId() == requestCode ) {
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        myPermission.setFlg(true);
                }
            }
        }
        int resultCounter = 0;
        for( MyPermission myPermission :  myPermission_list) {
            if ( !myPermission.getFlg() ) {
                if ( myPermission.getRequestCount() > 0 ) {
                    break;
                }
                ActivityCompat.requestPermissions(context,
                        new String[]{myPermission.getPermissionType()}, myPermission.getPermissionId());
                myPermission.setRequestCount(myPermission.getRequestCount()+1);
                break;
            }
            else {
                resultCounter++;
            }
        }
        if ( resultCounter == myPermission_list.size() ) {
            result = true;
        }

        return result;

    }

    public boolean checkPermission(){
        boolean result = false;
        int initCounter = 0;
        for( MyPermission myPermission :  myPermission_list) {
            if (ActivityCompat.checkSelfPermission(context, myPermission.getPermissionType())== PackageManager.PERMISSION_GRANTED){
                initCounter++;
                myPermission.setFlg(true);
            }
        }
        if ( initCounter == myPermission_list.size() ) {
            result = true;
        }
        else {
            for( MyPermission myPermission :  myPermission_list) {

                if ( !myPermission.getFlg() ) {
                    // 最初に許可されていないパーミッション用のダイアログを表示する
                    ActivityCompat.requestPermissions(context,
                            new String[]{myPermission.getPermissionType()}, myPermission.getPermissionId());
                    myPermission.setRequestCount(myPermission.getRequestCount()+1);

                    // 一つだけ処理する
                    break;
                }

            }
        }

        return result;
    }
}

対象となる Permission を ArrayList にセットとして内部で殆どの処理を実行してもらいます。ArrayList にセットする MyPermission クラスは以下のような情報を保持しています

MyPermission クラス
package com.example.lightbox.cameratest;

public class MyPermission {

    private String permissionType;
    private int permissionId;
    private boolean flg = false;
    private int requestCount = 0;

    public MyPermission(String type, int id) {
        permissionType = type;
        permissionId = id;
    }

    public String getPermissionType() {
        return permissionType;
    }

    public int getPermissionId() {
        return permissionId;
    }

    public boolean getFlg() {
        return flg;
    }

    public void setFlg(boolean flg) {
        this.flg = flg;
    }

    public int getRequestCount() {
        return requestCount;
    }

    public void setRequestCount(int requestCount) {
        this.requestCount = requestCount;
    }
}


主な処理はカメラと撮影した画像の保存ですが、カメラはテストを目的としているので 『非推奨』の 旧API を使用しています。

MainActivity
package com.example.lightbox.cameratest;

import android.Manifest;
import android.content.Intent;
import android.hardware.Camera;
import android.media.MediaScannerConnection;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.io.File;
import java.io.FileOutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;

public class MainActivity extends AppCompatActivity {

    private String imagePath;
    private OldCamera camera;
    private CheckMyPermission checkMyPermission;

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

        // 必要なパーミッションのリスト
        ArrayList<MyPermission> myPermission_list = new ArrayList<MyPermission>();
        myPermission_list.add(new MyPermission(Manifest.permission.CAMERA,100));
        myPermission_list.add(new MyPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE,101));
        // Runtime Permission 用のクラスのインスタンス
        checkMyPermission = new CheckMyPermission(MainActivity.this,myPermission_list);
        // 全ての必要なパーミッションが既に許可されていた場合
        if ( checkMyPermission.checkPermission() ) {
            cameraSettings();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        // 全ての必要なパーミッションが許可された場合
        if ( checkMyPermission.checkPermission( requestCode,  grantResults )) {
            // onCreate で初期処理できるように、MainActivity をリスタート
            Intent intent = getIntent();
            finish();
            startActivity(intent);
        }
    }

    private void cameraSettings() {

        camera = new OldCamera(MainActivity.this.findViewById(R.id.surfaceView) );

        // *************************************
        // ギャラリーに保存する処理
        // *************************************
        Button galleryButton = (Button) MainActivity.this.findViewById(R.id.button);
        galleryButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("lightbox", "クリック");

                // 撮影
                camera.getCamera().takePicture(null, null, new Camera.PictureCallback() {
                    @Override
                    public void onPictureTaken(byte[] data, Camera camera) {

                        if (data != null) {

                            // ギャラリー用に内部ストレージにフォルダを作成
                            String imageDir = Environment.getExternalStorageDirectory().getPath() + "/cameratest";
                            File file = new File(imageDir);
                            // ディレクトリ初期作成
                            if (!file.exists()) {
                                if (file.mkdir() == false) {
                                    Log.i("lightbox", "ディレクトリを作成できませんでした");
                                    return;
                                }
                            }

                            // ギャラリー用画像保存パス
                            Calendar cal = Calendar.getInstance();
                            SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
                            imagePath = imageDir + "/" + sf.format(cal.getTime()) + ".jpg";

                            FileOutputStream jpg;
                            try {
                                jpg = new FileOutputStream(imagePath);
                                jpg.write(data);
                                jpg.close();

                                // ギャラリーに反映
                                MediaScannerConnection.scanFile(
                                        MainActivity.this,
                                        new String[] { imagePath },
                                        new String[] { "image/jpeg" },
                                        null);

                            }
                            catch (Exception e) {
                                e.printStackTrace();
                            }

                            // カメラ機能再開
                            camera.startPreview();

                        }
                    }
                });

            }
        });
    }

}


OldCamera クラス
package com.example.lightbox.cameratest;

import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import java.io.IOException;
import java.util.List;

public class OldCamera implements SurfaceHolder.Callback {

    // 古いカメラAPI。カメラで何かしたいわけでは無いのでこれで実装
    private Camera camera;
    private SurfaceHolder surfaceHolder;
    private List<Camera.Size> sizeList;

    public OldCamera(View view) {

        Log.i("lightbox", "コンストラクタ");

        // SurfaceView から holder を取り出す
        surfaceHolder = ((SurfaceView)view).getHolder();
        // この中でイベント処理を行う
        surfaceHolder.addCallback(OldCamera.this);

    }

    public Camera getCamera() {

        return camera;

    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

        Log.i("lightbox", "開く");

        // カメラを開く
        camera = Camera.open();
        try {
            // カメラに holder を渡す
            camera.setPreviewDisplay(holder);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        // 縦固定(AndroidManifest.xml) で、portrait 撮影を前提
        camera.setDisplayOrientation(90);

        // カメラの情報
        Camera.Parameters params = camera.getParameters();
        // サポートされているサイズの一覧
        sizeList = params.getSupportedPreviewSizes();

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

        // 縦固定(AndroidManifest.xml) なので一度しか通らない
        Log.i("lightbox", "開始");

        // 縦横サイズの設定
        Camera.Parameters params = camera.getParameters();
        Camera.Size optimalSize = getOptimalPreviewSize(sizeList,width,height);
        params.setPreviewSize(optimalSize.width,optimalSize.height);
        camera.setParameters(params);

        camera.startPreview();

    }

    // http://qiita.com/zaburo/items/b5d3815d3ec45b0daf4f
    private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

        final double ASPECT_TOLERANCE = 0.1;
        double targetRatio=(double)h / w;

        if (sizes == null) return null;

        Camera.Size optimalSize = null;
        double minDiff = Double.MAX_VALUE;

        int targetHeight = h;

        for (Camera.Size size : sizes) {
            double ratio = (double) size.width / size.height;
            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }

        if (optimalSize == null) {
            minDiff = Double.MAX_VALUE;
            for (Camera.Size size : sizes) {
                if (Math.abs(size.height - targetHeight) < minDiff) {
                    optimalSize = size;
                    minDiff = Math.abs(size.height - targetHeight);
                }
            }
        }
        return optimalSize;
    }


    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

        Log.i("lightbox", "終了");

        // プレビュー終了
        if ( camera != null ) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }

    }
}


画面
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.lightbox.cameratest.MainActivity">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Button" />
    </LinearLayout>

    <SurfaceView
        android:id="@+id/surfaceView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.lightbox.cameratest">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


posted by lightbox at 2017-06-06 20:56 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

Android 6.0 の Runtime Permission に対応する前に、AndroidManifest.xml に権限の記述の必要無いプライベートな書き込みで情報を収集する

Android の中でなかなかテキストファイルに書き込む機会も無いので、FileOutputStream と SharedPreferences でカメラで撮った画像を外部ストレージに書き込むのに知っておきたい情報の確認をしています。

ボタンのイベント

一般的に3種類あるボタンのイベントの作成方法で、ボタンの id によって処理を分けていく結果になる使い方です。ソースとしては自然と連続になるので、このような場合( Write と Read ) では第三者から見たら解りやすくなると思います。(MainActivity に implements View.OnClickListener が必要ですね)

※ 他の二つは、無名(匿名)のインナーでその場に書いてしまう方法と、ボタンのプロパティにメソッド名登録して、MainActivity の メソッドとしてイベントする方法です。


書き込み処理では、BufferedWriter はほぼ無駄なので使っていません。最後に改行コードつけておけば、BufferedReader で読めますし。

書き込んだ後は、Android Device Monitor で push してリアルに確認するのがいいですね。Android に騙されないで、なんか安心します。



test.txt

※ test.txt は、files フォルダのの下にあります
ストレージ情報
/storage/emulated/0
android.permission.CAMERA
android.permission.WRITE_EXTERNAL_STORAGE
0

Environment.getExternalStorageDirectory().getPath()/storage/emulated/0 が取得できています。ここに書き込みするためには Android 6.0 では Runtime Permission の処理が必要になってくるわけですが、今は場所のみを Android Device Monitor で確認しておきます。
package com.example.lightbox.textfiletest;

import android.Manifest;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        Log.i("lightbox", "implements View.OnClickListener したら、setOnClickListener(this)");
        findViewById(R.id.button).setOnClickListener(this);
        findViewById(R.id.buttonRead).setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {

        // View から ID を取得して仕分け
        if (v.getId() == R.id.button ) {

            Log.i("lightbox", "AndroidManifest.xml に権限の記述の必要無いプライベートな書き込み");
            try {
                FileOutputStream fileOutputStream = openFileOutput("test.txt", MODE_PRIVATE);
                OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, "utf-8");

                // 次の段階のストレージアクセスの為の情報集め
                outputStreamWriter.write("ストレージ情報\n");
                outputStreamWriter.write(String.format("%s\n", Environment.getExternalStorageDirectory().getPath()));
                // String
                outputStreamWriter.write(Manifest.permission.CAMERA + "\n");
                outputStreamWriter.write(Manifest.permission.WRITE_EXTERNAL_STORAGE + "\n");
                // int
                outputStreamWriter.write(PackageManager.PERMISSION_GRANTED + "\n");

                outputStreamWriter.close();
                fileOutputStream.close();

                // プライベートな小さな情報を保存する為の本来の方法
                SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
                SharedPreferences.Editor editor = data.edit();

                // 現在の年月日・時分秒を取得する為の準備
                Calendar cal = Calendar.getInstance();
                SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");

                editor.putString("now", sf.format(cal.getTime()));
                editor.commit();

            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        else if ( v.getId() == R.id.buttonRead ) {

            Log.i("lightbox", "読み込み");
            try {
                FileInputStream fileINputStream = openFileInput("test.txt");
                InputStreamReader inputStreamReader = new InputStreamReader(fileINputStream, "utf-8");
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

                String line_buffer;
                while( null != (line_buffer = bufferedReader.readLine()) ) {
                    // 長さ 0 で Empty
                    if (line_buffer.isEmpty()) {
                        continue;
                    }

                    Log.i("lightbox",line_buffer);

                }

                inputStreamReader.close();
                fileINputStream.close();

                SharedPreferences data = getSharedPreferences("private_data", MODE_PRIVATE);
                Log.i("lightbox", data.getString("now", "このデータはありません"));

            }
            catch (Exception e) {
                e.printStackTrace();
            }

        }

    }
}

Manifest.permission.CAMERA や Manifest.permission.WRITE_EXTERNAL_STORAGE は、Runtime Permission のチェックで使う定数です。これを ActivityCompat.checkSelfPermission に渡して利用可能の有無をまずチェックします。(実際は継承元の ContextCompat のメソッド)



posted by lightbox at 2017-06-06 11:23 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

2017年05月28日


Android Studio : エミュレータで Notification(通知)のテスト



こんな感じで結果的には表示されます。単なる動作確認のテストなので、MainActivity でボタンをクリックしたイベント内で呼び出しています。呼び出す前のステータスバーは、以下のようになります。



ボタンをクリックすると、以下のようになります。



このステータスバー(通知領域)から、下にドラッグ(エミュレータなので)すると通知画面が開きます。開いた通知をクリックすると、設定しておいた Google のドキュメントページをブラウザで開きます


Develop > API Guides > 通知

   public void notification(View view) {

        Log.i("lightbox", "notification");
        Notification.Builder builder = new Notification.Builder(getApplicationContext());

        // 通知内容の作成
        builder.setSmallIcon(android.R.drawable.ic_dialog_info);
        builder.setContentTitle("通知テスト");
        builder.setContentText("通知内容");
        builder.setSubText("説明");
        builder.setContentInfo("右下");
        builder.setPriority(Notification.PRIORITY_DEFAULT);
        builder.setAutoCancel(true);

        // 通知から呼び出される処理の作成
        Uri uri = Uri.parse("https://developer.android.com/guide/topics/ui/notifiers/notifications.html?hl=ja");
        // ブラウザ呼び出し
        Intent intent = new Intent(Intent.ACTION_VIEW,uri);
        // 通知に情報を与える PendingIntent の作成
        PendingIntent pendingIntent = PendingIntent.getActivity(MainActivity.this,0, intent,PendingIntent.FLAG_CANCEL_CURRENT);
        // 通知にアクションを設定
        builder.setContentIntent(pendingIntent);

        // NotificationManager で通知
        NotificationManager manager = (NotificationManager)MainActivity.this.getSystemService(NOTIFICATION_SERVICE);
        // 第一引数は、アプリケーション内でユニークな通知の識別子
        manager.notify(0, builder.build());

    }

ボタンは今回は、単純に Button の属性で指定しています。public void メソッド(View view) な定義ならなんでもいいです。







posted by lightbox at 2017-05-28 15:44 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

2017年05月26日


Android Studio : BroadcastReceiver を Anonymous Inner Class で使用し、バッテリーの状態をエミュレータで簡単にテスト

▼ log.i の出力
I/lightbox: android.intent.action.BATTERY_CHANGED
I/lightbox: level : 10 ( / 100 )
I/lightbox: GOOD
I/lightbox: android.intent.action.BATTERY_CHANGED
I/lightbox: level : 50 ( / 100 )
I/lightbox: GOOD
I/lightbox: android.intent.action.BATTERY_CHANGED
I/lightbox: level : 100 ( / 100 )
I/lightbox: GOOD
内部クラスや、サブクラスで実装してもいいですが、内容としてはあまりしょっちゅう行うものでも無いし、テストとしては、telnet で adb に power capacity するほうが重要なのでこのような感じになりました。 BroadcastReceiver に代入する処理は、通常のイベント作成と同じく、new の後で CTRL+SPACE で候補を表示させて、BroadcastReceiver を選択すれば自動的に public void onReceive が作成されるので、その中に処理を記述するたけです。
package com.example.lightbox.broadcasttest;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    private BroadcastReceiver broadcastReceiver;

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

    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.i("lightbox", "onResume");

        broadcastReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {

                String action = intent.getAction();
                Log.i("lightbox", action);

                int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL,-1);
                int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE,-1);
                Log.i("lightbox", String.format("level : %d ( / %d )",level,scale));
                int health = intent.getIntExtra(BatteryManager.EXTRA_HEALTH,-1);
                switch( health ) {
                    case BatteryManager.BATTERY_HEALTH_COLD:
                        Log.i("lightbox", "COLD");
                        break;
                    case BatteryManager.BATTERY_HEALTH_DEAD:
                        Log.i("lightbox", "DEAD");
                        break;
                    case BatteryManager.BATTERY_HEALTH_GOOD:
                        Log.i("lightbox", "GOOD");
                        break;
                    case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE:
                        Log.i("lightbox", "OVER_VOLTAGE");
                        break;
                    case BatteryManager.BATTERY_HEALTH_OVERHEAT:
                        Log.i("lightbox", "OVERHEAT");
                        break;

                }

            }
        };

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(MainActivity.this.getIntent().ACTION_BATTERY_CHANGED);
        // レシーバーを登録
        MainActivity.this.registerReceiver(broadcastReceiver,intentFilter);

    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.i("lightbox", "onPause");

        MainActivity.this.unregisterReceiver(broadcastReceiver);
    }
}

実際には switch 部分すら必要ありませんが、Android のドキュメントを覘くためのヒントとして使っています。

1) BatteryManager
2) BatteryManager.EXTRA_LEVEL

adb.exe がある場所

Android Studio で実行して、エミュレータ上で実行されたら、タスクマネージャでプロセスを表示してから、イメージ名をクリックしてソートすると、adb.exe がほぼ一番上に表示されるので、右クリックから『ファイルの場所を開く』を選択すると、でエクスプローラで開く事ができます



さらに、エクスプローラでそのフォルダを選択して SHIFT キーを押しながら右クリックして、『コマンドウインドウをここで開く』を選択します。

telnet を使えるように

既に使用している場合は必要無いですが、昨今めったに使う事が無いので Windows で使用できないようになっています。『プログラムと機能』の左サイドにある『Windows の機能の有効化または無効化』で開いたツリーで telnet を有効にします



コマンドプロンプトより adb にアクセス

まず、adb devices をコマンドプロンプトで実行して、現在のエミュレータの id を確認します。
List of devices attached
emulator-5554   device
次に、telnet localhost 5554 を実行して、telnet で adb にアクセスします。
Android Console: Authentication required
Android Console: type 'auth ' to authenticate
Android Console: you can find your  in
'C:\Users\lightbox\.emulator_console_auth_token'
ここで、まず auth コマンドでトークンを入力する必要があります。表示された場所にある、.emulator_console_auth_token をテキストエディタで開いてトークンを取得し、auth コマンドで実行します。
auth トークン文字列
Android Console: type 'help' for a list of commands
OK
これでコマンドが使えるようになるので、help と入力して確認します
help
Android console command help:

    help|h|?         print a list of commands
    event            simulate hardware events
    geo              Geo-location commands
    gsm              GSM related commands
    cdma             CDMA related commands
    crash            crash the emulator instance
    kill             kill the emulator instance
    network          manage network settings
    power            power related commands
    quit|exit        quit control session
    redir            manage port redirections
    sms              SMS related commands
    avd              control virtual device execution
    qemu             QEMU-specific commands
    sensor           manage emulator sensors
    finger           manage emulator finger print
    debug            control the emulator debug output tags
    rotate           rotate the screen clockwise by 90 degrees

try 'help ' for command-specific help
使用するのは、power コマンドです。
power
allows to change battery and AC power status

available sub-commands:
    display          display battery and charger state
    ac               set AC charging state
    status           set battery status
    present          set battery present state
    health           set battery health state
    capacity         set battery capacity state
power capacity 10 のようにして、バッテリーの量を変更するコマンドをクリップボードにコピーしておいて、貼り付けて実行してみて下さい。エミュレータの右上のバッテリーアイコンの量の表示が変わります。 すると、LogCat にも表示されるはずです
posted by lightbox at 2017-05-26 21:58 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

2017年05月14日


Android Studio : OkHttp v3.8.0 で WEBアプリに POST 送信を行う

関連する記事

OkHttp v3.8.0 jar( と okio 1.13.0.jar ) を Eclipse のプロジェクトに追加して一般的な POST 送信を行う

Eclipse の一般的な Java からの送信を AsynTask を使用して Android Studio から送信しました。送信する文字列は、ArrayList にセットして、AsyncTask<ArrayList<String>,Void,String> として定義して引き渡しています。
package com.example.lightbox.posttest;

import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import java.util.ArrayList;

import okhttp3.Call;
import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

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

        Button button = (Button) MainActivity.this.findViewById(R.id.button);
        button.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {

        if ( v.getId() == R.id.button) {
            Log.i("lightbox", "クリックされました");
            CallPost();

        }

    }

    private void CallPost() {

        ArrayList<String> data = new ArrayList<String>();
        data.add("winofsql@gmail.com");
        data.add("OkHttp で送信テスト(Android Studio)");
        data.add("日本語\r\n表示");

        AsyncTask<ArrayList<String>,Void,String> async;
        // 非同期実行用オブジェクト
        async = new AsyncTask<ArrayList<String>, Void, String>() {
            @Override
            protected String doInBackground(ArrayList<String>... params) {

                ArrayList<String> data = params[0];

                // HTTP 処理用オプジェクト
                OkHttpClient client = new OkHttpClient();

                // POST 用 FormBody の内容の作成
                FormBody.Builder formbodyBuilder = new FormBody.Builder();
                formbodyBuilder.add("to", data.get(0));
                formbodyBuilder.add("subject", data.get(1));
                formbodyBuilder.add("body", data.get(2));

                // 送信用ユニットの作成
                FormBody formbody = formbodyBuilder.build();

                // 送信用のデータを作成
                Request.Builder requestBuilder = new Request.Builder();
                String url = "https://ドメイン/lightbox/mail/send.php";
                requestBuilder.url(url);
                requestBuilder.post(formbody);
                Request request = requestBuilder.build();

                // 受信用のオブジェクトの準備
                Call call = client.newCall(request);
                String result = "";

                // 送信と受信
                try {

                    Response response = call.execute();
                    result = response.body().string();

                } catch (Exception e) {
                    e.printStackTrace();
                }

                return result;
            }

            @Override
            protected void onPostExecute(String s) {
                Log.i("lightbox", s);
                // 画面に対してのアクセスはここから行います


            }
        };

        // 実行
        async.execute(data);

    }

}


Module の build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.example.lightbox.posttest"
        minSdkVersion 23
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
    compile 'com.squareup.okhttp3:okhttp:3.8.0'
}

※ AndroidManifest.xml には、<uses-permission android:name="android.permission.INTERNET" /> を追加。


posted by lightbox at 2017-05-14 20:07 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

2017年03月31日


Android Studio の .android .AndroidStudio2.x .gradle フォルダの場所を『ほぼ変更』する

変更した後、プロジェクトをロードすると、エラーが出たりしましたが、プロジェクト内のキャッシュが元のどこかを指してるようだったので、clean project(build) で対応できました。

前提条件

Windwos7 64ビットです。

検証環境は、Android Studio 2.3 ( Windows ) です。インストールファイルは、『android-studio-bundle-162.3764568-windows.exe Android SDK を含む(推奨)』です。

まずインストールフォルダを 『D:\Android Studio』にして、SDK フォルダを 『D:\sdk』で開始しています。

※ 過去バージョンのダウンロードはこちらです

結論から言うと

.AndroidStudio2.3 フォルダと、.gradle フォルダは消えてくれません。中身はそっくり D に移ったのですが、とてもバグっぽい挙動で勝手にフォルダを作成してしまうようです。

▼ こんな記録もありますし
Android Studio creates empty system directory in the default location after idea.system.path is set to a new location

.android フォルダは、インストール時に使うようで、それらしいファイルが残ってましたので、何かあった時の為に残していますが、詳しい検証・調査は行っていません。



.android フォルダは、エミュレータフォルダが作成される場所なので、それは完全に移動されています。

.android フォルダ

これはたぶん一般的なもので、ANDROID_SDK_HOME=D:\sdkhome という環境変数を作成して移動しました。D:\sdkhome は事前に作成して実行すると、設定する前に作成したエミュレータは AVD マネージャから見えなくなりました。元々のファイル郡は、.android\avd の中にありましたが、トラブルは嫌なので新規で作成しました。



※ エミュレータが作成されるので、ここが容量をとても喰うと思います。

.AndroidStudio2.3 フォルダ

これを実行するとこんなダイアログが出ました。



Android Studio がバージョンアップ時にいつも表示するアレです。


ここの中身を移動したい場合は、Android Studio\bin\idea.properties の中の設定を変更します
#
# *DO NOT* modify this file directly. If there is a value that you would like to override,
# please add it to your user specific configuration file.
#
# See http://tools.android.com/tech-docs/configuration
#
# Use ${idea.home.path} macro to specify location relative to IDE installation home.
# Use ${xxx} where xxx is any Java property (including defined in previous lines of this file) to refer to its value.
# Note for Windows users: please make sure you're using forward slashes (e.g. c:/idea/system).

#---------------------------------------------------------------------
# Uncomment this option if you want to customize path to IDE config folder. Make sure you're using forward slashes.
#---------------------------------------------------------------------
# idea.config.path=${user.home}/.AndroidStudio/config
idea.config.path=D:/sdkhome/idea/.AndroidStudio2.3/config

#---------------------------------------------------------------------
# Uncomment this option if you want to customize path to IDE system folder. Make sure you're using forward slashes.
#---------------------------------------------------------------------
# idea.system.path=${user.home}/.AndroidStudio/system
idea.system.path=D:/sdkhome/idea/.AndroidStudio2.3/system


idea.config.path と idea.system.path を直接変更しています。何も指定しないと、user.home は USERPROFILE 環境変数の値になるようです(${xxx} where xxx is any Java property)。で、その場所に、studio64.exe.vmoptions ファイルの中にある、-Didea.paths.selector=AndroidStudio2.3 と言う設定を使っていました。
#
# *DO NOT* modify this file directly. If there is a value that you would like to override,
# please add it to your user specific configuration file.
#
# See http://tools.android.com/tech-docs/configuration
#
-Xms256m
-Xmx1280m
-XX:ReservedCodeCacheSize=240m
-XX:+UseConcMarkSweepGC
-XX:SoftRefLRUPolicyMSPerMB=50
-da
-Djna.nosys=true
-Djna.boot.library.path=

-Djna.debug_load=true
-Djna.debug_load.jna=true
-Dsun.io.useCanonCaches=false
-Djava.net.preferIPv4Stack=true
-Didea.paths.selector=AndroidStudio2.3
-Didea.platform.prefix=AndroidStudio
-Didea.jre.check=true


これは、単純に文字列を結合するようです。-Didea.paths.selector=D:/sdkhome/idea/.AndroidStudio2.3 としたら、C:\Users\lightbox\.D:/sdkhome/idea/.AndroidStudio2.3 というパスでエラーが出ました。

idea.config.path と idea.system.path を指定した上で、-Didea.paths.selector=D:/sdkhome/idea/.AndroidStudio2.3 ならば、エラーは起こらずに .AndroidStudio2.3 フォルダは作成されません。が、これはたまたまエラー処理がスルーされただけなので、しないほうがいいでしょう。

面倒なので、idea.config.path と idea.system.path の指定のみにする事にしました。

.gradle フォルダ

これは、Android Studio の設定で指定します。



ただ、これも勝手に C:\Users\ユーザ名\.gradle\daemon\3.3 という空のフォルダを作成しちゃってますので注意して下さい。

参考にした記事

Changing IDE default directories used for config, plugins, and caches storage



タグ:Android Studio
posted by lightbox at 2017-03-31 18:53 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

2017年01月27日


Android Studio で、javamail を gradle に書いて添付ファイル付きメール送信を行う

数年前までは、Google Code の 2009 年ライブラリを使用して Android 用のメール送信を行っていたのですが、

参照 ➡ javamail-android + AsyncTask でメール送信を行う為のテンプレート

今回改めて調べてみると、オリジナルの javamail 側で Android の対応がなされていました。gradle に記述するだけで使用可能です。

▼ 今回使用したもの
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "january17.lightbox.mailapplication"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    packagingOptions {
        pickFirst 'META-INF/LICENSE.txt' // picks the JavaMail license file
    }
}

repositories {
    jcenter()
    maven {
        url "https://maven.java.net/content/groups/public/"
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.sun.mail:android-mail:1.5.5'
    compile 'com.sun.mail:android-activation:1.5.5'
    testCompile 'junit:junit:4.12'
}


さらに、今回は添付ファイルを付加する為に調べてみると、MimeBodyPart クラスの attachFile メソッドに、ファイルのパスを渡すだけで全てライブラリが実行してくれる事が解りました。さっそくオリジナルのソースコードを読んでみると、古くからあるサンプルの FileDataSource を使った内容(Google code バージョンでも同等の処理をしていました)であり、さらに Google code バージョンには無い contentType と encoding を指定できる attachFile メソッドも存在していました。

添付ファイル用 AndroidSendmail クラス
public class AndroidSendmail {

	private String server;
	private String port;
	private String userid;
	private String password;
	private String username;

	// ************************************
	// コンストラクタ
	// ************************************
	public AndroidSendmail(
		String server,
		String port,
		String userid,
		String password,
		String username) {

		this.server = server;
		this.port = port;
		this.userid = userid;
		this.password = password;
		this.username = username;
	}

	// ************************************
	// AsyncTask の onPostExecute から外部イベントとして
	// 呼び出す為のインターフェイス
	// ************************************
	public interface SendMailed {
		public void onMailResult(String result);
	}

	// ************************************
	// メール送信
	// ************************************
	public void SendMail(String to, String from, String subject, String body,String file, final SendMailed sm) {

		new AsyncTask<String, Void, String>() {

			// ************************************
			// 非同期処理
			// ************************************
			@Override
			protected String doInBackground(String... params) {

				Log.i("lightbox","開始");

				String result_string = "";
				try {
					// ************************************
					// プロパティオブジェクトを作成
					// プロパティオブジェクトは、extends Hashtable(連想配列)
					// ************************************
					Properties props = new Properties();

					// ************************************
					// * 連想配列に送信用サーバのアドレスをセット
					// ************************************
//					props.put("mail.smtp.host","smtp.mail.yahoo.co.jp");	// Yahoo
//					props.put("mail.smtp.host","smtp.live.com");	// Microsoft
					props.put("mail.smtp.host", server);
					props.put("mail.smtp.port", port);
					props.put("mail.smtp.auth", "true" );	// SMTP 認証を行う

					// ************************************
					// SSL関連設定
					// ************************************
					if ( port.equals("465") ) {
						props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
						props.put("mail.smtp.socketFactory.fallback", "false");
						props.put("mail.smtp.socketFactory.port", port);
					}

					// ************************************
					// 暗号化
					// ************************************
					if ( port.equals("587") ) {
						props.put("mail.smtp.starttls.enable", "true");
					}


					// ************************************
					// メール用のセッションを作成
					// ************************************
					SimpleAuthenticator sa =
						new SimpleAuthenticator(userid, password);
					Session MailSession =
						Session.getInstance( props, sa );

					// ************************************
					// メール用のメッセージオブジェクトを作成
					// ************************************
					MimeMessage msg = new MimeMessage(MailSession);

					// ************************************
					// 宛先
					// ( 第一引数では、CC や BCC を指定できます。)
					// ************************************
					msg.setRecipients(
						Message.RecipientType.TO,
						new Address[] { new InternetAddress( params[0], "日本語相手先", "ISO-2022-JP" ) }
					);

					// ************************************
					// 送信者
					// ************************************
					msg.setFrom(
						new InternetAddress( params[1], username, "ISO-2022-JP" )
					);

					// ************************************
					// 件名
					// ************************************
					msg.setSubject( params[2], "ISO-2022-JP" );

					// ************************************
					// 本文(テキスト)
					// ************************************
					MimeBodyPart mbp1 = new MimeBodyPart();
					mbp1.setText(params[3], "ISO-2022-JP");

					// ************************************
					// 添付ファイル
					// ************************************
					MimeBodyPart mbp2 = new MimeBodyPart();
					mbp2.attachFile(params[4]);

					// ************************************
					// マルチパート作成
					// ************************************
					Multipart mp = new MimeMultipart();
					mp.addBodyPart(mbp1);
					mp.addBodyPart(mbp2);

					msg.setContent(mp);

					// ************************************
					// 送信
					// ************************************
					Transport.send( msg );

					result_string = "メールの送信を完了しました";

				}
				catch( Exception e ) {
					e.printStackTrace();
					result_string = "メールの送信に失敗しました";
				}

				Log.i("lightbox","終了");

				return result_string;
			}

			// ************************************
			// 非同期処理終了後の処理( 画面へのアクセスが可能 )
			// ************************************
			@Override
			protected void onPostExecute(String result) {
				// 引数のインターフェイス内のメソッドを呼び出す
				sm.onMailResult(result);
			}

		}.execute(to, from, subject, body,file);

	}

	// ************************************
	// 認証用のプライベートクラス
	// ************************************
	private class SimpleAuthenticator extends Authenticator {

		private String user_string = null;
		private String pass_string = null;

		public SimpleAuthenticator( String user_s, String pass_s ) {
			super();
			user_string = user_s;
			pass_string = pass_s;
		}

		protected PasswordAuthentication getPasswordAuthentication(){
			return new PasswordAuthentication( this.user_string, this.pass_string );
		}
	}
}

添付するファイルは、ギャラリーから取得する画像を使ってテストしました。昨今、Android では、ギャラリーから取得する為の intent のパラメータが過去バージョンから変わっており、取得する uri も 直接のパスでは無い為、作法にのっとってデーターベースより必要な情報を収集しています。

MainActivity

※ ここでは、MIME と 画像の表示名を DB から取得していますが、使用していません
※ Gmail を使用する場合は、安全性の低いアプリの許可を『有効』にする 必要があります。
public class MainActivity extends AppCompatActivity {

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

		Button button = (Button) MainActivity.this.findViewById(R.id.button);
		button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				// ギャラリーの呼び出し
				Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
				intent.addCategory(Intent.CATEGORY_OPENABLE);
				intent.setType("image/*");
				startActivityForResult(intent, 10);        // 10 は任意

			}
		});
	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		if (requestCode == 10 && resultCode == Activity.RESULT_OK) {
			if (data != null) {
				Uri uri = data.getData();

				String id = DocumentsContract.getDocumentId(uri);
				Log.i("lightbox", "id文字列:" + id);

				// カラムリストの指定
				String[] proj = {
					MediaStore.Images.Media.DATA,
					MediaStore.Images.Media.MIME_TYPE,
					MediaStore.Images.Media.DISPLAY_NAME
				};
				String selection = "_id=?";
				String[] args = new String[]{id.split(":")[1]};

				// 目的データのカーソルを取得
				ContentResolver contentResolver = getContentResolver();
				Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, proj, selection, args, null);
				cursor.moveToFirst();    // 読み出し

				// データを取得
				String result = cursor.getString(0);
				String mime = cursor.getString(1);
				String name = cursor.getString(2);
				Log.i("lightbox", "Path:" + result);
				cursor.close();


				AndroidSendmail as = new AndroidSendmail(
					"smtp.gmail.com", // サーバ( 例 : "smtp.mail.yahoo.co.jp" or "smtp.live.com" )
					"465", // 465 または 587
					"アカウント", // アカウント
					"パスワード", // パスワード
					"日本語ユーザ名"
				);

				as.SendMail(
					"宛先メールアドレス",
					"自分のメールアドレス",
					"件名",
					"本文一行目\n本文二行目",
					result, // ファイルへのパス
					new AndroidSendmail.SendMailed() {
						@Override
						public void onMailResult(String result) {
							Log.i("lightbox", result);
						}
					}
				);

			}
		}
	}
}


上のコードでは、Android API 19 以上で利用できる、getDocumentId を使用していますが、それより古いバージョンでは 呼び出し時に ACTION_GET_CONTENT を使用して、取得できる uri の パスの一番最後の数値を _id に使用して取得できました。
※ API 18(4.3.1) と API 15(4.0.3) を エミュレータで確認しました。
Uri uri = data.getData();

Log.i("lightbox", "id文字列:" + uri.toString());

// カラムリストの指定
String[] proj = {
	MediaStore.Images.Media.DATA,
	MediaStore.Images.Media.MIME_TYPE,
	MediaStore.Images.Media.DISPLAY_NAME
};
String selection = "_id=?";
String[] path = (uri.toString()).split("/");
String[] args = new String[]{path[path.length-1]};


Manifest.xml

※ INTERNET と READ_EXTERNAL_STORAGE が必要です
<?xml version="1.0" encoding="utf-8"?>
<manifest package="january17.lightbox.mailapplication"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

最後に

マルチパートの内容を WEB上の Outlook から送って比べてみました。Microsoft は、異常に多くの情報を持っていましたが、一般的なデータは javamail の setText(本文, "ISO-2022-JP") と attachFile(ファイルのパス) で問題無いようでした。(ヘッダ部分は通常の WEB メールで確認できます)
▼ Microsoft()
--_002_PS1PR01MB0667CE2FF5CECC981B23A8CF81710PS1PR01MB0667apcp_
Content-Type: text/plain; charset="iso-2022-jp"
Content-Transfer-Encoding: quoted-printable

=1B$BK\J80l9TL\=1B(B
=1B$BK\J8Fs9TL\=1B(B

--_002_PS1PR01MB0667CE2FF5CECC981B23A8CF81710PS1PR01MB0667apcp_
Content-Type: image/jpeg; name="2013-01-26T01-26-00_0.jpg"
Content-Description: 2013-01-26T01-26-00_0.jpg
Content-Disposition: attachment; filename="2013-01-26T01-26-00_0.jpg";
	size=431072; creation-date="Fri, 20 Jan 2017 16:29:33 GMT";
	modification-date="Fri, 20 Jan 2017 16:29:33 GMT"
Content-Transfer-Encoding: base64

/9j/4AAQSkZJRgABAQAAAQABAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAABAAEAAIdpAAQA
AAABAAAAJgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAGYKADAAQAAAABAAAEyAAAAAD/2wBD
AAIBAQIBAQICAQICAgICAwUDAwMDAwYEBAMFBwYHBwcGBgYHCAsJBwgKCAYGCQ0JCgsLDAwMBwkN

▼ JavaMail for Android
------=_Part_0_1107074864.1484932123775
Content-Type: text/plain; charset=ISO-2022-JP
Content-Transfer-Encoding: quoted-printable

=1B$BK\J80l9TL\=1B(B
=1B$BK\J8Fs9TL\=1B(B

------=_Part_0_1107074864.1484932123775
Content-Type: image/jpeg; name=KIMG0178.JPG
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=KIMG0178.JPG

/9j/4TjERXhpZgAATU0AKgAAAAgACgEPAAIAAAAIAAAAhgEQAAIAAAAGAAAAjgESAAMAAAABAAMA
AAEaAAUAAAABAAAAlAEbAAUAAAABAAAAnAEoAAMAAAABAAIAAAExAAIAAAALAAAApAEyAAIAAAAU
AAAAsAITAAMAAAABAAEAAIdpAAQAAAABAAAAxAAABEpLWU9DRVJBADQwNEtDAAAAAEgAAAABAAAA



posted by lightbox at 2017-01-27 21:29 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする

2017年01月23日


Android 内の画像データの一覧を CSV に出力する

出力場所として、USB 参照できる場所にフォルダを作成してその中に CSV ファイルを書き込むようにします。おそらく最新の CSV を USB 経由で確認するには、Android 本体を再起動する必要があると思いますが、パッケージ内部に作成するといろいろ面倒なので、こうした上で Android Device Monitor から pull で 取り出すのがいいと思います。
	private String okhttp_folder;
	private String[] columnNames;

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


		// USB から参照可能な場所にフォルダを作成
		// 但し、更新直後のデータのアクセスは、Device Monitor
		// USB からの場合は Device を再起動
		okhttp_folder = Environment.getExternalStorageDirectory().getPath() + "/okhttp";
		File file = new File(okhttp_folder);
		// ディレクトリ初期作成
		if (!file.exists()) {
			if (file.mkdir() == false) {
				Log.i("lightbox", "ディレクトリを作成できませんでした");
			}
		}

		Button button = (Button) MainActivity.this.findViewById(R.id.button);
		button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				writeCsv("image_data.csv");
			}
		});
	}


CSV ファイルを作成しやすいよう(カンマの付加を単純にする為)に、固定で row_no を先頭に追加しています。最初に列名リストを取得していますが、この際、カーソルを取得するだけで取り出し可能です。データ部分は moveToFirst と moveToNext が必要ですが、一行で実行するために三項演算子を使用しています(初回のみ null で以降 true か false)。

結果の内容を Excel で開いた様子
	private void writeCsv( String filename ) {

		ContentResolver contentResolver = getContentResolver();
		Uri listing = MediaStore.Images.Media.getContentUri("external");
		Log.i("lightbox", "Content URI : " + listing.toString());
		// ▲ content://media/external/images/media
		Cursor cursor;

		cursor	= contentResolver.query(listing, null, null, null, null);

		// 行バッファ
		StringBuilder builder = new StringBuilder();

		// 列名一覧
		String[] columnNames = cursor.getColumnNames();
/*
		_id
		_data
		_size
		_display_name
		mime_type
		title
		date_added
		date_modified
		description
		picasa_id
		isprivate
		latitude
		longitude
		datetaken
		orientation
		mini_thumb_magic
		bucket_id
		bucket_display_name
		width
		height
*/

		// CSV データを作成
		builder.append("row_no");
		for ( int i = 0; i < columnNames.length; i++ ) {
			builder.append(",");
			builder.append(columnNames[i]);
		}
		builder.append("\n");
		cursor.close();

		try {
			FileOutputStream stream = new FileOutputStream(new File(okhttp_folder + "/" + filename));
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream));

			cursor = contentResolver.query(listing, null, null, null, null);
			int columnCount = cursor.getColumnCount();
			int rowCount = 0;

			// 画像のデータを取得
			Boolean isData = null;
			while ( isData == null ? (isData = cursor.moveToFirst()) : (isData = cursor.moveToNext()) ) {

				rowCount++;
				builder.append(rowCount);
				for ( int i = 0; i < columnCount; i++ ) {
					builder.append(",");
					builder.append(cursor.getString(i));
				}
				builder.append("\n");

			}

			// 全てを書込み
			writer.write(builder.toString());

			writer.flush();
			writer.close();
			Log.i("lightbox","CSV出力完了");

		} catch (Exception e) {
			e.printStackTrace();
		}

	}



posted by lightbox at 2017-01-23 22:47 | Comment(0) | Android Studio 2017 | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

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

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

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

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


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

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

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

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

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


Windows
container 終わり

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

Android SDK ポケットリファレンス
改訂版 Webデザイナーのための jQuery入門
今すぐ使えるかんたん ホームページ HTML&CSS入門
CSS ドロップシャドウの参考デモ
PHP正規表現チェッカー
Google Hosted Libraries
cdnjs
BUTTONS (CSS でボタン)
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり