トップ・ページの表示 注意書きの表示 掲示板に書き込む前に必ず この ”注意書き”を お読み下さい.

"伊邪那"

   
   

ページの表示順:{ 新しい順/ 古い順}.
初期・ページの表示・位置:{ 先頭ページ/ 末尾ページ}.
1ページ内のスレッド表示数:











<Number>: [000009CA]  <Date>: 2016/03/22 05:39:54
<Title>: Tetraz
<Name>: amanojaku@管理人



中々プレイしやすいテトリスが無いので自分で作ってみる。
ちなみに「Tetris(R)」は登録商標「(R)」になっているようなので、名前は「Tetraz」としています。

「Medium」モードの場合、「黄色(yellow)、紫(purple)」の図形の出現率を少し多めに、「Easy」モードの場合、「水色(cyan)、黄色(yellow)、紫(purple)」の図形の出現率を少し多めにしています。
プレイ中の「メニュー・キー、戻る・キー」の誤操作 対策として「メニュー・キー、戻る・キー」が押された場合はデフォルトの処理は無効化され、(2秒間) Toast が表示され、その間(2秒間)ゲームの Thread も Sleep します。
以前 作成した「SurfaceView 028」を元に やっつけ仕事でプログラムを作成したので、Class 設計としてはチョット ダメな感じ。

とりあえず、[New Project]で(1ページ目)[Application name]:「Tetraz」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<ユーザー名>\AndroidStudioProjects\SurfaceView」と設定、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
ただし、「<プロジェクト名>→app→build.gradle」内の"targetSdkVersion"がデフォルト値だと「Android 2.2(API Level 8)」の端末にインストールできない場合があるようだ。
「<プロジェクト名>→app→build.gradle」内の"targetSdkVersion"を(当方は SDK に「API Level 10:Android OS 2.3.3」をインストールしているので)「10」(API Level 10:Android OS 2.3.3)に設定してやると「Android 2.2(API Level 8)」の端末にインストールできるようになる場合があるようだ。
低いバージョンの端末まで対応したい場合は、 SDK に「API Level 10:Android OS 2.3.3」を(「Android Studio」ではなくオリジナルの「Android SDK」の「SDK Manager.exe」で)インストールしておくことをオススメする。
なお、「targetSdkVersion」はアプリ側が(OS 側の)どのAPIレベルまでサポートしているかと言うことらしい。

このスレの下の方にに"drawable-hdpi"フォルダー用のアイコン・ファイル(「48x48」Pixel)をアップしてあるので、「drawable-hdpi」フォルダー(存在しない場合は作成してやる)内にコピペして下さい。
一応、アプリの Logo マークを Android のマスコット・キャラの「Droid君」にしている。
"drawable-mdpi"フォルダー用は手抜きしているが、解像度が低い端末でも自動的に その端末の解像度に合うように縮小して表示される。
その場合、当然 画質は悪くなるので画質を綺麗にしたい場合は手抜きせずにチャント"drawable-mdpi"フォルダー用の画像ファイル(「32x32」Pixel)も用意してやる。
「Toolbar」の UI はスタンダードな「ActionBarActivity」のような感じになっている。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。



『<Project名>→app→main→java→com.example.tetraz→MainActivity』(MainActivity.java)


package com.example.tetraz;

import android.os.Bundle;
import android.util.Log;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
// import android.widget.Toolbar;
import android.content.Context;

import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;

import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import android.view.Gravity;
import android.view.View;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import android.widget.ImageButton;
import android.view.KeyEvent;
import android.view.MotionEvent;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Bitmap;
import android.widget.Toast;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity
        implements View.OnClickListener, View.OnTouchListener, SurfaceHolder.Callback {
    static final String fsDebugCrest = " 021";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = false; // true; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。
    MainActivity oMainApp;
    Context oBaseContext;
    Context oAppContext;
    Context oContext;
    ActionBar oActionBar;
    Menu oMainMenu;

    String vsPackageName;
    String vsVersionName;
    int iVersionCode;
    String vsToolbarTitle;

    enum AppLaunchMode_Enum { feCreate, feRestart };
    static AppLaunchMode_Enum eAppLaunchMode;

    // String vsMessage = "";
    // TextView ctvMessage;
    RunningSurfaceView oRunningSurfaceView;

    static final int[] ONTOUCH_WIDGETS = new int[]{
            R.id.cibBlockRight, R.id.cibBlockLeft,
            R.id.cibBlockDown, R.id.cibBlockRotate, };
    ImageButton cibBlockRight, cibBlockLeft, cibBlockDown;

    enum TetrazBlocks_Enum { feConflictCheck, feDraw, feErase, feDeposit,
        feSqueezeCheck, feSqueeze };
    static final int [][][] d3sTetrazBlockColorRID = new int[][][]{
            {
                    { R.color.cyan, R.color.cyan, R.color.cyan, R.color.cyan, },
            },
            {
                    { R.color.blue, R.color.empty, R.color.empty, },
                    { R.color.blue, R.color.blue, R.color.blue, },
            },
            {
                    { R.color.empty, R.color.empty, R.color.orange, },
                    { R.color.orange, R.color.orange, R.color.orange, },
            },
            {
                    { R.color.yellow, R.color.yellow, },
                    { R.color.yellow, R.color.yellow, },
            },
            {
                    { R.color.empty, R.color.lawngreen, R.color.lawngreen, },
                    { R.color.lawngreen, R.color.lawngreen, R.color.empty, },
            },
            {
                    { R.color.empty, R.color.purple, R.color.empty, },
                    { R.color.purple, R.color.purple, R.color.purple, },
            },
            {
                    { R.color.red, R.color.red, R.color.empty, },
                    { R.color.empty, R.color.red, R.color.red, },
            },
    };
    static final int [] d1iTetrazBlockRoll_Default = new int[]{
            1, 3, 1, 0, 1, 1, 1,
    };
    static volatile ArrayList<RunnerBlocks> dl1oRunnerBlocksDepot; // = new ArrayList<RunnerBlocks>( );

    public abstract class RunnerPrimitive {

        public void run() {
        }
    }

    public abstract class RunnerFigure extends RunnerPrimitive {
        // volatile:最適化の抑制.
        volatile RunningSurfaceView oRunningSurfaceView;
        volatile Paint oPaint = new Paint();
        volatile double vdPX, vdPY, vdDX, vdDY;
    }

    public class RunnerBlocks extends RunnerFigure {

        final int [][][] d3sBlockColorRID = d3sTetrazBlockColorRID;

        // volatile:最適化の抑制.
        // volatile Paint oPSurface = new Paint();
        // volatile int iBlockPattern;
        volatile int iCBlockPattern = 0;
        // volatile int iOBlockPattern = -1;
        volatile int iEBlockRoll, iCBlockRoll, iOBlockRoll;
        volatile int iSCX, iSCY;
        volatile int iSOX, iSOY;
        volatile int iSEDX, iSEDY;
        volatile int iSCDX, iSCDY;
        volatile int iSNX, iSNY;
        // volatile boolean lEHorizonActivated = false; // true; //
        volatile int iBlocksAreaSWMin, iBlocksAreaSWMax;
        volatile int iBlocksAreaSHMin, iBlocksAreaSHMax;
        volatile int iBlocksTouchSXMin, iBlocksTouchSXMax;
        volatile int iBlocksUnTouchSXMin, iBlocksUnTouchSXMax;
        volatile boolean lBlocksConflict = false; // true; //
        volatile boolean lShowSW = true; // false; //
        volatile boolean lFirstStrike = true; // false; //
        // volatile boolean lDepositSW = false; // true; //
        volatile boolean lDestroy = false; // true; //
        // volatile boolean lCBlockRoll = false; // true; //
        volatile boolean lEBlockRoll = false; // true; //
        // volatile boolean lEDrawSW, lCDrawSW;

        int iBlockRoll_Default;
        boolean[] d1lConflict = new boolean[5];

        RunnerBlocks(RunningSurfaceView oRSV, int iCBP, int iCBR) {
            super( );
            oRunningSurfaceView = oRSV;
            if( 0<=iCBP ) {
                iCBlockPattern = iCBP;
            }else {
                // ???
                iCBlockPattern = (int)Math.floor(
                        Math.random() * d3sBlockColorRID.length);
            }
            Initialize(iCBR);
        }

        public void Initialize(int iCBR){
            RunningSurfaceView oRSV = oRunningSurfaceView;
            // ???
            if(DEBUG)Log.i(tag, "RunnerBlocks( ) : "+
                    "iCBlockPattern="+iCBlockPattern+"; "+
                    "d3sBlockColorRID.length="+d3sBlockColorRID.length+"; "+
                    "");
            if( 0<=iCBR ) {
                iEBlockRoll = iCBR;
                iCBlockRoll = iCBR;
                iOBlockRoll = iCBR;
            }else{
                iEBlockRoll = d1iTetrazBlockRoll_Default[iCBlockPattern];
                iCBlockRoll = iEBlockRoll;
                iOBlockRoll = iEBlockRoll;
            }
            lBlocksConflict = false; // true; //
            lShowSW = true; // false; //
            lFirstStrike = true; // false; //
            lDestroy = false; // true; //
            // lCBlockRoll = false; // true; //
            lEBlockRoll = false; // true; //
            iSCX = oRSV.iWallSWQnt / 2;
            iSCY = 0;
            iSOX = iSCX ;
            iSOY = iSCY;
            iSEDX = 0;
            iSEDY = 0;
            iSCDX = iSEDX;
            iSCDY = iSEDY;
        }

        void BlockRotate( ){
            iEBlockRoll++;
            if( 4<=iEBlockRoll ){
                iEBlockRoll = 0;
            }
        }

        @Override
        public void run() {
            RunningSurfaceView oRSV = oRunningSurfaceView;
            RunnerBlocks  oNewRunnerBlocks = null;
            TetrazBlocks_Enum eTB;
            int iSAX, iSAY, iNBlockPattern;
            int iSRX, iSRY, iSBX, iSBY, iABlockRoll;
            boolean lABlockRoll = false; // true; //
            lDestroy = false; // true; //
            boolean lBlockRoll = false; // true; //
            // int iSCDX, iSCDY;
            if( ! lFirstStrike ){
                FigureDraw( oRSV.iWallScreenBiasX, oRSV.iWallScreenBiasY,
                        oRSV.iWallSWQnt, oRSV.iWallSHQnt,
                        iCBlockPattern, iSOX, iSOY, iOBlockRoll,
                        TetrazBlocks_Enum.feErase );
            }
            if( lEBlockRoll ){
                BlockRotate();
            }
            lABlockRoll = lEBlockRoll;
            lEBlockRoll = false; // true; //
            // lCBlockRoll = false; // true; //
            iABlockRoll = iEBlockRoll;
            lBlockRoll = false; // true; //
            iSCDX = iSEDX;
            iSCDY = iSEDY;
            iSAX = iSCX+iSCDX;
            iSAY = iSCY+iSCDY;
            GetBlockArea(iCBlockPattern, iABlockRoll);
            FigureDraw( oRSV.iWallScreenBiasX, oRSV.iWallScreenBiasY,
                    oRSV.iWallSWQnt, oRSV.iWallSHQnt,
                    iCBlockPattern, iSAX, iSAY, iABlockRoll,
                    TetrazBlocks_Enum.feConflictCheck );
            if(DEBUG)Log.i(tag, "run( ) : "+
                    "lEBlockRoll="+lEBlockRoll+"; "+
                    "lABlockRoll="+lABlockRoll+"; "+
                    "iEBlockRoll="+iEBlockRoll+"; "+
                    "iABlockRoll="+iABlockRoll+"; "+
                    "iCBlockRoll="+iCBlockRoll+"; "+
                    "");
            if( ! oRSV.lDepositSW ) {
                if (lBlocksConflict) {
                    if( lABlockRoll ){
                        if( 0<iBlocksTouchSXMin && iBlocksTouchSXMax<0 ){
                            lBlockRoll = true; // false; //
                        }
                        if( iBlocksTouchSXMin<0 && iBlocksTouchSXMax<0 ){
                            iSAX = iSAX - (iBlocksAreaSWMin - iBlocksTouchSXMin) + 1;
                        }
                        if( 0<iBlocksTouchSXMin && 0<iBlocksTouchSXMax ){
                            if(DEBUG)Log.i(tag, "run( ) : "+
                                    "iSAX="+iSAX+"; "+
                                    "iBlocksAreaSWMax="+iBlocksAreaSWMax+"; "+
                                    "iBlocksTouchSXMax="+iBlocksTouchSXMax+"; "+
                                    "(iBlocksAreaSWMax-iBlocksTouchSXMax)="+(iBlocksAreaSWMax - iBlocksTouchSXMax)+"; "+
                                    "");
                            iSAX = iSAX - (iBlocksAreaSWMax - iBlocksTouchSXMax) - 1;
                        }
                        if( ! lBlockRoll ){
                            FigureDraw( oRSV.iWallScreenBiasX, oRSV.iWallScreenBiasY,
                                    oRSV.iWallSWQnt, oRSV.iWallSHQnt,
                                    iCBlockPattern, iSAX, iSAY, iABlockRoll,
                                    TetrazBlocks_Enum.feConflictCheck );
                        }
                        if( ! lBlocksConflict ||
                                0<iBlocksTouchSXMin && iBlocksTouchSXMax<0 ){
                            lBlockRoll = true; // false; //
                        }else {
                            lBlockRoll = false; // true; //
                            iEBlockRoll = iCBlockRoll;
                            iABlockRoll = iCBlockRoll;
                            iSAX = iSCX + iSCDX;
                            iSAY = iSCY + iSCDY;
                            if ( lFirstStrike ) {
                                lDestroy = true; // false; //
                                // oRSV.EngineDestroy();
                            }
                            // lCBlockRoll = lBlockRoll;
                        }
                    }else{
                        if (0 != iSCDX) {
                            iSAX = iSCX;
                        }
                        if ( lFirstStrike ) {
                            lDestroy = true; // false; //
                            // oRSV.EngineDestroy( );
                        } else if (0 != iSCDY) {
                            oRSV.lDepositSW = true; // false; //
                            oRSV.lRepaintSW = true; // false; //
                            iSAY = iSCY;
                            GetBlockArea(iCBlockPattern, iABlockRoll);
                            if( 0>(iSAY + iBlocksAreaSHMin) ){
                                lDestroy = true; // false; //
                            }else {
                                oNewRunnerBlocks = oRSV.dl1oRunnerBlocksStock.get(0);
                                oNewRunnerBlocks.Initialize(0);
                                oRSV.dl1oRunnerBlocksStock.remove(0);
                                oRSV.dl1oRunnerBlocksStock.add(oRSV.oBlockDepotDelivery(-1));
                            }
                        }
                    }
                }
            }
            iSCX = iSAX;
            iSCY = iSAY;
            iCBlockRoll = iABlockRoll;
            if(DEBUG)Log.i(tag, "run( ) : "+
                    "iCBlockPattern="+iCBlockPattern+"; "+
                    "iSCDX="+iSCDX+"; "+
                    "iSCDY="+iSCDY+"; "+
                    "iSAY="+iSAY+"; "+
                    "iSCX="+iSCX+"; "+
                    "iSCY="+iSCY+"; "+
                    "iBlocksAreaSWMin="+iBlocksAreaSWMin+"; "+
                    "iBlocksAreaSWMax="+iBlocksAreaSWMax+"; "+
                    "");
            eTB = TetrazBlocks_Enum.feDraw;
            if( oRSV.lDepositSW ){
                eTB = TetrazBlocks_Enum.feDeposit;
            }
            FigureDraw( oRSV.iWallScreenBiasX, oRSV.iWallScreenBiasY,
                    oRSV.iWallSWQnt, oRSV.iWallSHQnt,
                    iCBlockPattern, iSCX, iSCY, iCBlockRoll,
                    eTB );
            iOBlockRoll = iCBlockRoll;
            iSOX = iSCX;
            iSOY = iSCY;
            if( null!=oNewRunnerBlocks ){
                oRSV.oRunnerBlocks.Initialize(-1);
                dl1oRunnerBlocksDepot.add(oRSV.oRunnerBlocks);
                oRSV.oRunnerBlocks = oNewRunnerBlocks;
            }
            // lCBlockRoll = false; // true; //
            lFirstStrike = false; // true; //
            if(lDestroy){
                // Toast.makeText(oMainApp, "GAME OVER", Toast.LENGTH_SHORT).show();
                oRSV.EngineDestroy( );
            }
        }

        public void BlockBullet(int iBiasX, int iBiasY, int iSAX, int iSAY, Paint oPaint) {
            RunningSurfaceView oRSV = oRunningSurfaceView;
            int iRX, iRY;
            iRX = iBiasX + iSAX * oRSV.iBlockDistance;
            iRY = iBiasY + iSAY * oRSV.iBlockDistance;
            oRSV.oCOffScreen.drawRect(
                    iRX, iRY,
                    iRX + oRSV.iBlockDistance - 1, iRY + oRSV.iBlockDistance - 1,
                    oPaint);
        }

        public void GetBlockArea( int iBlockPattern, int iBRoll ){
            int iAX, iAY, iSAX, iSAY, iSRX, iSRY, iSBX, iSBY, iFOdd, iFXSign, iFYSign;
            double vdFBX, vdFBY, vdSBX, vdSBY ,vdSNX, vdSNY, vdSRX, vdSRY;
            double vdBASIWMin, vdBASIWMax, vdBASIHMin, vdBASIHMax;
            double vdBASFWMin, vdBASFWMax, vdBASFHMin, vdBASFHMax;
            int iBASFWMin, iBASFWMax, iBASFHMin, iBASFHMax;
            iFOdd = iBRoll % 2;
            iFXSign = 1; iFYSign = 1;
            if( 0!=iBRoll / 2 ){
                iFXSign = -1;
                iFYSign = -1;
            }
            if( 0!=iFOdd ){
                iFXSign = -iFXSign;
            }
            vdSBX = (double)-(d3sBlockColorRID[iBlockPattern][0].length - 1) / 2;
            vdSBY = (double)-(d3sBlockColorRID[iBlockPattern].length - 1) / 2;

            vdBASIWMin = vdSBX;
            vdBASIWMax = vdSBX + d3sBlockColorRID[iBlockPattern][0].length - 1;
            vdBASIHMin = vdSBY;
            vdBASIHMax = vdSBY + d3sBlockColorRID[iBlockPattern].length - 1;
            if( 0==iFOdd ){
                vdBASFWMin = iFXSign * vdBASIWMin;
                vdBASFWMax = iFXSign * vdBASIWMax;
                vdBASFHMin = iFYSign * vdBASIHMin;
                vdBASFHMax = iFYSign * vdBASIHMax;
            }else{
                vdBASFWMin = iFXSign * vdBASIHMin;
                vdBASFWMax = iFXSign * vdBASIHMax;
                vdBASFHMin = iFYSign * vdBASIWMin;
                vdBASFHMax = iFYSign * vdBASIWMax;
            }
            iBASFWMin = (int)Math.round(vdBASFWMin + .3);
            iBASFWMax = (int)Math.round(vdBASFWMax + .3);
            iBASFHMin = (int)Math.round(vdBASFHMin + .3);
            iBASFHMax = (int)Math.round(vdBASFHMax + .3);
            iBlocksAreaSWMin = Math.min(iBASFWMin, iBASFWMax);
            iBlocksAreaSWMax = Math.max(iBASFWMin, iBASFWMax);
            iBlocksAreaSHMin = Math.min(iBASFHMin, iBASFHMax);
            iBlocksAreaSHMax = Math.max(iBASFHMin, iBASFHMax);
        }

        public void FigureDraw(int iBiasX, int iBiasY, int iSWidth, int iSHeight,
                               int iBlockPattern, int iSPX, int iSPY, int iBRoll,
                               TetrazBlocks_Enum oTetrazBlocks) {
            RunningSurfaceView oRSV = oRunningSurfaceView;
            Paint oP = null;
            int iAX, iAY, iSRX, iSRY, iSBX, iSBY, iFOdd, iFXSign, iFYSign;
            double vdFBX, vdFBY, vdSBX, vdSBY ,vdSIX, vdSIY, vdSFX, vdSFY;
            double vdBASNWMin, vdBASNWMax, vdBASNHMin, vdBASNHMax;
            double vdBASRWMin, vdBASRWMax, vdBASRHMin, vdBASRHMax;
            int iBASRWMin, iBASRWMax, iBASRHMin, iBASRHMax;
            int iSFX, iSFY;
            boolean lConflict = false; // true; //
            boolean lConflictCheck = false; // true; //
            int iBTSXMin,iBTSXMax;
            lBlocksConflict = false; // true; //
            for( int i = 0; i<d1lConflict.length; i++ ){
                d1lConflict[i] = false; // true; //
            }

            iFOdd = iBRoll % 2;
            iFXSign = 1; iFYSign = 1;
            if( 0!=iBRoll / 2 ){
                iFXSign = -1;
                iFYSign = -1;
            }
            if( 0!=iFOdd ){
                iFXSign = -iFXSign;
            }
            vdSBX = (double)-(d3sBlockColorRID[iBlockPattern][0].length - 1) / 2;
            vdSBY = (double)-(d3sBlockColorRID[iBlockPattern].length - 1) / 2;

            iBTSXMin = 1;
            iBTSXMax = -1;
            iBlocksTouchSXMin = 1;
            iBlocksTouchSXMax = -1;
            GetBlockArea(iBlockPattern, iBRoll);
            BlockPattern : for(int iStepY = 0; iStepY < d3sBlockColorRID[iBlockPattern].length; iStepY++) {
                for(int iStepX = 0; iStepX < d3sBlockColorRID[iBlockPattern][iStepY].length; iStepX++) {
                    if( R.color.empty!=d3sBlockColorRID[iBlockPattern][iStepY][iStepX] ) {
                        vdSIX = vdSBX + iStepX;
                        vdSIY = vdSBY + iStepY;
                        if( 0==iFOdd ){
                            vdSFX = iFXSign * vdSIX;
                            vdSFY = iFYSign * vdSIY;
                        }else{
                            vdSFX = iFXSign * vdSIY;
                            vdSFY = iFYSign * vdSIX;
                        }
                        iSRX = (int)Math.round(iSPX + vdSFX + .3);
                        iSRY = (int)Math.round(iSPY + vdSFY + .3);
                        if( TetrazBlocks_Enum.feDraw==oTetrazBlocks ||
                                TetrazBlocks_Enum.feDeposit==oTetrazBlocks ) {
                            oP = oPaint;
                            oP.setColor(ContextCompat.getColor(oAppContext,
                                    d3sBlockColorRID[iBlockPattern][iStepY][iStepX]));
                            oP.setStyle(Style.FILL);
                        }
                        if( TetrazBlocks_Enum.feErase==oTetrazBlocks ) {
                            oP = oRSV.oPWall;
                        }
                        if( TetrazBlocks_Enum.feConflictCheck==oTetrazBlocks ){
                            lConflict = (iSRX<0 || iSWidth<=iSRX) ||
                                    (iSHeight<=iSRY);
                            lConflict = lConflict || (0<=iSRX && iSRX<iSWidth) &&
                                    (0<=iSRY && iSRY<iSHeight) &&
                                    (oRSV.oPWall.getColor()!=oRSV.d2iBlockPoolColorARGB[iSRX][iSRY]);
                            if( lConflict ){
                                lBlocksConflict = true; // false; //
                                iSFX = (int)Math.round(vdSFX + .3);
                                // iSFY;
                                d1lConflict[d1lConflict.length / 2 + iSFX] = true;
                            }
                        }else if( (0<=iSRX && iSRX<iSWidth) &&
                                (0<=iSRY && iSRY<iSHeight) ) {
                            if( TetrazBlocks_Enum.feConflictCheck==oTetrazBlocks ) {
                                if( oRSV.oPWall.getColor()!=oRSV.d2iBlockPoolColorARGB[iSRX][iSRY] ) {
                                    lBlocksConflict = true; // false; //
                                    // break BlockPattern;
                                }
                            }else{
                                BlockBullet(iBiasX, iBiasY, iSRX, iSRY, oP);
                                if( TetrazBlocks_Enum.feDeposit==oTetrazBlocks ) {
                                    oRSV.d2iBlockPoolColorARGB[iSRX][iSRY] = oP.getColor();
                                }
                            }
                        }else if(0<=iSRY){
                            if( TetrazBlocks_Enum.feConflictCheck==oTetrazBlocks ) {
                                lBlocksConflict = true; // false; //
                                break BlockPattern;
                            }
                        }
                    }
                }
            }
            if(lBlocksConflict){
                for( int i = 0; i<d1lConflict.length; i++ ){
                    iSFX = i - d1lConflict.length / 2;
                    if(d1lConflict[i]){
                        if( iSFX<=0 ){
                            iBlocksTouchSXMin = iSFX;
                        }
                        if( 0<=iSFX ){
                            if( -1==iBlocksTouchSXMax ){
                                iBlocksTouchSXMax = iSFX;
                            }
                        }
                    }
                    if(DEBUG)Log.i(tag, "FigureDraw( ) : "+
                            "i="+i+"; "+
                            "iSFX="+iSFX+"; "+
                            "iBlocksTouchSXMin="+iBlocksTouchSXMin+"; "+
                            "iBlocksTouchSXMax="+iBlocksTouchSXMax+"; "+
                            "");
                }
            }
        }
    }

    public abstract class RunningDriver
            implements Runnable {

        // volatile:最適化の抑制.
        // volatile ArrayList<RunnerFigure> dl1oRunnerMembers = new ArrayList<RunnerFigure>( );
        // volatile ArrayList<RunnerBlocks> dl1oRunnerBlocks = new ArrayList<RunnerBlocks>( );
        volatile RunnerBlocks oRunnerBlocks;
        // volatile ArrayList<RunnerBlocks> dl1oRunnerBlocksDeposit = new ArrayList<RunnerBlocks>( );
        volatile ArrayList<RunnerBlocks> dl1oRunnerBlocksStock; // = new ArrayList<RunnerBlocks>( );

        // volatile int iRunnerNumber;
        volatile boolean lLaunchActivated = false; // true; //
        volatile boolean lEngineSW = false; // true; //
        volatile boolean lWaitSW = false; // true; //
        volatile boolean lVirginGround = true; // false; //
        volatile boolean lClearSW = true; // false; //
        volatile boolean lDepositSW = false; // true; //
        volatile boolean lRepaintSW = false; // true; //
        volatile Thread oEngine = null;
        volatile long interval = 0;
        int iInterval_HorizonOrder = 225;
        int iInterval_SqueezeOrder = 600;
        int iInterval_DefaultDropOrder = 700;
        int iInterval_RollOrder = 550;
        int iInterval_DepositOrder = 50;
        int iInterval_FastDropOrder = 80;
        volatile int iESleepInterval = -1;
        volatile int iCSleepInterval = -1;
        int iInterval_ReleaseOrder = 300;
        int iHorizonMinuteOrderMax = 0;
        int iDropMinuteOrderMax = 0;
        // int iDefaultMinuteOrderMax = iDropMinuteOrderMax;
        int iEventMinuteOrderMax = -1;

        volatile int iMinuteOrderDCnt = -1;
        volatile int iDropMinuteOrderCnt = 0;
        volatile int iDropStep = 0;
        // volatile boolean lEHorizonActivated = false; // true; //
        // volatile boolean lEHorizonListening = false; // true; //
        // volatile boolean lEHorizonReceive = false; // true; //
        // volatile boolean lEHorizonOrder = false; // true; //
        // volatile boolean lCHorizonAction = false; // true; //
        // volatile boolean lHorizonOrder = false; // true; //
        volatile int iETouchAction_BlockRightPress = -1;
        volatile int iCTouchAction_BlockRightPress = -1;
        volatile int iETouchAction_BlockRightRelease = -1;
        volatile int iCTouchAction_BlockRightRelease = -1;
        volatile int iETouchAction_BlockLeftPress = -1;
        volatile int iCTouchAction_BlockLeftPress = -1;
        volatile int iETouchAction_BlockLeftRelease = -1;
        volatile int iCTouchAction_BlockLeftRelease = -1;
        volatile int iETouchAction_BlockDown = -1;
        volatile int iCTouchAction_BlockDown = -1;
        int iWallScreenBiasX, iWallScreenBiasY;
        int iWallSWQnt = 11;
        int iWallSHQnt = 19;

        public RunningDriver( ) {
            super( );
        }

        // abstract public boolean lPoolBottomCheck( );
        abstract public boolean lBlockPoolBufSqueeze( TetrazBlocks_Enum oTB );

        public void Launch(AppLaunchMode_Enum eLaunchMode) {
            lLaunchActivated = true; // false; //
            if( AppLaunchMode_Enum.feCreate==eLaunchMode ){
                Initialize( );
                EngineCreate(true); // false; //
            }
            if( AppLaunchMode_Enum.feRestart==eLaunchMode ){
                EngineCreate(true); // false; //
            }
        }

        public void Initialize( ){
            lLaunchActivated = false; // true; //
            lEngineSW = true; // false; //
            lWaitSW = false; // true; //
            lClearSW = true; // false; //
            lVirginGround = true; // false; //
            lRepaintSW = false; // true; //
            lDepositSW = false; // true; //
            iETouchAction_BlockRightPress = -1;
            iCTouchAction_BlockRightPress = -1;
            iETouchAction_BlockRightRelease = -1;
            iCTouchAction_BlockRightRelease = -1;
            iETouchAction_BlockLeftPress = -1;
            iCTouchAction_BlockLeftPress = -1;
            iETouchAction_BlockLeftRelease = -1;
            iCTouchAction_BlockLeftRelease = -1;
            iETouchAction_BlockDown = -1;
            iCTouchAction_BlockDown = -1;
            iESleepInterval = -1;
            iCSleepInterval = -1;
        }

        public abstract void RunningHeader( );

        public abstract void RunningFooter( );

        public void RunnerWait(boolean lWSW) {
            lWaitSW = lWSW;
            if (DEBUG) Log.i(tag,
                    "lWaitSW="+String.valueOf(lWaitSW)+"; "+
                            "; ");
            if( null!=oEngine ) {
// NEW:起動していないスレッド、
// RUNNABLE:実行されているスレッド、
// BLOCKED:ブロックされモニターロックを待機しているスレッド、
// WAITING:特定のアクションを実行するのを無期限に待機しているスレッド、
// TIMED_WAITING:指定された待機時間、ほかのスレッドがアクションを実行するのを待機しているスレッド、
// TERMINATED:終了したスレッドの状態、
                if (DEBUG) Log.i(tag,
                        "oEngine.getState="+String.valueOf(oEngine.getState( ))+
                                "; ");
                if( ! lWaitSW ){
                    synchronized(this) {
                        try {
// 「notify( )」は synchronized で囲わないと正常に機構しないようだ。
// (「wait( )」などで)待機中の場合は お互いに synchronized ブロックでも synchronized ブロックを通過できるらしい。
// 「notify( )」は この synchronized ブロック内では実行されず、
// この synchronized ブロック通過後にイベント的に「notify( )」コマンドが発行される。
                            notify( );
                        }catch(IllegalMonitorStateException e){
                            if(DEBUG)Log.i(tag, "RunningDriver#RunnerWait( ) : IllegalMonitorStateException; "+
                                    "");
                        }
                    }
                }
            }
        }

        public void EngineCreate( boolean lWSW ) {
            lEngineSW = true; // false; //
            lWaitSW = lWSW; // false; // true; //
            lClearSW = true; // false; //
            lVirginGround = true; // false; //
            lRepaintSW = false; // true; //
            if (DEBUG) Log.i(tag, "oEngine = new Thread(this)");
            oEngine = new Thread(this);
            oEngine.start();
        }

        public void EngineDestroy( ) {
            if( null!=oEngine ){
                oEngine.interrupt( );
            }
        }

        public void run() {
            if (DEBUG) Log.i(tag, "RunningDriver:run( );");
            RunnerBlocks oRB;
            boolean lDrawSW = true; // false; //
            boolean lSqueeze, lASqueeze; // false; // true; //
            boolean lPress = false; // true; //

            while ( oEngine!=null && lEngineSW ) {
                // lCHorizonAction = lEHorizonAction;
                iCTouchAction_BlockRightPress = iETouchAction_BlockRightPress;
                iCTouchAction_BlockLeftPress = iETouchAction_BlockLeftPress;
                iCTouchAction_BlockRightRelease = iETouchAction_BlockRightRelease;
                iCTouchAction_BlockLeftRelease = iETouchAction_BlockLeftRelease;
                iCTouchAction_BlockDown = iETouchAction_BlockDown;
                iCSleepInterval = iESleepInterval;
                // oRunnerBlocks.lCBlockRoll = oRunnerBlocks.lEBlockRoll;
                // oRunnerBlocks.lEBlockRoll = false;

                oRunnerBlocks.iSEDX = 0;
                oRunnerBlocks.iSEDY = 0;
                lASqueeze = false; // true; //
                lSqueeze = false; // true; //
                lDrawSW = false; // true; //
                interval = iInterval_DefaultDropOrder;
                lASqueeze = lBlockPoolBufSqueeze( TetrazBlocks_Enum.feSqueezeCheck );;
                if( 0<=iCSleepInterval ) {
                    interval = iCSleepInterval;
                    iESleepInterval = -1;
                }else if( lASqueeze ) {
                    lSqueeze = lASqueeze;
                    oRunnerBlocks.lEBlockRoll = false; // true; //
                    interval = iInterval_SqueezeOrder;
                }else {
                    lDrawSW = true; // false; //
                    if ( ! oRunnerBlocks.lFirstStrike ) {
                        oRunnerBlocks.iSEDY = 1;
                    }
                    if(oRunnerBlocks.lEBlockRoll){
                        oRunnerBlocks.iSEDY = 0;
                        // oRunnerBlocks.BlockRotate( );
                        interval = iInterval_RollOrder;
                    }
                    if ( lPress && MotionEvent.ACTION_UP==iCTouchAction_BlockRightRelease ) {
                        if (DEBUG) Log.i(tag, "if ( MotionEvent.ACTION_UP==iCTouchAction_BlockRightRelease );");
                        iETouchAction_BlockRightRelease = -1;
                        oRunnerBlocks.iSEDY = 0;
                        interval = iInterval_ReleaseOrder;
                    }
                    if ( lPress && MotionEvent.ACTION_UP==iCTouchAction_BlockLeftRelease ) {
                        if (DEBUG) Log.i(tag, "if( MotionEvent.ACTION_UP==iCTouchAction_BlockLeftRelease );");
                        iETouchAction_BlockLeftRelease = -1;
                        oRunnerBlocks.iSEDY = 0;
                        interval = iInterval_ReleaseOrder;
                    }
                    lPress = false; // true; //
                    if ( MotionEvent.ACTION_DOWN==iCTouchAction_BlockRightPress ||
                            cibBlockRight.isPressed( ) ) {
                        if (DEBUG) Log.i(tag, "if( MotionEvent.ACTION_DOWN==iCTouchAction_BlockRight );");
                        iETouchAction_BlockRightPress = -1;
                        lPress = true; // false; //
                        oRunnerBlocks.iSEDX = 1;
                        oRunnerBlocks.iSEDY = 0;
                        interval = iInterval_HorizonOrder;
                    }
                    if ( MotionEvent.ACTION_DOWN==iCTouchAction_BlockLeftPress ||
                            cibBlockLeft.isPressed()) {
                        if (DEBUG) Log.i(tag, "if( MotionEvent.ACTION_DOWN==iCTouchAction_BlockLeft );");
                        iETouchAction_BlockLeftPress = -1;
                        lPress = true; // false; //
                        oRunnerBlocks.iSEDX = -1;
                        oRunnerBlocks.iSEDY = 0;
                        interval = iInterval_HorizonOrder;
                    }
                    if (MotionEvent.ACTION_DOWN == iCTouchAction_BlockDown ||
                            cibBlockDown.isPressed()) {
                        if (DEBUG) Log.i(tag, "if(cibBlockDown.isPressed( ));");
                        iETouchAction_BlockDown = -1;
                        interval = iInterval_FastDropOrder;
                        if ( oRunnerBlocks.lFirstStrike ) {
                            lDrawSW = false; // true; //
                            // oRunnerBlocks.iSEDY = 0;
                        }
                    }
                }
                if(DEBUG)Log.i(tag, "run( ) : "+
                        "interval="+interval+"; "+
                        "");
                if( 0!=oRunnerBlocks.iSEDX ){
                    if(DEBUG)Log.i(tag, "if( 0!=oRunnerBlocks.iSEDX ) : "+
                            "oRunnerBlocks.iSEDX="+oRunnerBlocks.iSEDX+"; "+
                            "");
                }
                // lWaitSW = true; // false; //
                try {
                    if( lSqueeze ){
                        if(DEBUG)Log.i(tag, "run( ) : lBlockPoolBufSqueeze( ) : "+
                                "interval="+interval+"; "+
                                "");
                        RunningHeader();
                        lBlockPoolBufSqueeze(TetrazBlocks_Enum.feSqueeze);
                        RunningFooter();
                    }else if( lDrawSW ) {
                        RunningHeader();
                        // for (int iRunner = 0; iRunner < dl1oRunnerMembers.size(); iRunner++) {
                        // if ( null!=dl1oRunnerMembers.get(iRunner) ) {
                        // dl1oRunnerMembers.get(iRunner).run();
                        // }
                        // }
                        if( ! lVirginGround ){
                            lVirginGround = false; // true; //
                            oRunnerBlocks.run();
                            if ( lDepositSW ) {
                                lDepositSW = false; // true; //
                                // lDrawSW = true; // false; //
                                interval = iInterval_DepositOrder;
                                if(DEBUG)Log.i(tag, "run( ) : if ( lDepositSW )"+
                                        "interval="+interval+"; "+
                                        "");
                            }
                        }
                        RunningFooter();
                    }
                } catch (NullPointerException e) {
                    // ????
                    // e.printStackTrace( ); // ???
                    if (DEBUG) Log.i(tag, "RunningDriver:run( ):NullPointerException;");
                    lEngineSW = false; // true; //
                }
                lVirginGround = false; // true; //
                if(DEBUG)Log.i(tag, "run( ) :  "+
                        "interval="+interval+"; "+
                        "lSqueeze="+lSqueeze+"; "+
                        "");
                synchronized (this) {
                    // ↑ この this は oEngine では無く RunningDriver を継承している オブジェクトのインスタンス。
                    if (lWaitSW) {
                        try {
                            // 「wait( )」は synchronized で囲わないと正常に機構しない。
                            this.wait( );
                            if (DEBUG) Log.i(tag, "RunningDriver:wait( ):End.");
                            // ↑ この this は oEngine では無く RunningDriver を継承している オブジェクトのインスタンス。
                        } catch (InterruptedException e) {
                            // e.printStackTrace( );
                            if (DEBUG) Log.i(tag, "RunningDriver:run( ):InterruptedException;");
                            lEngineSW = false; // true; //
                        }
                    }
                    if (lEngineSW) {
                        if(DEBUG)Log.i(tag, "run( ) :  Thread.sleep( ) : "+
                                "interval="+interval+"; "+
                                "");
                        try {
                            Thread.sleep(interval);
                            // ↑ 「sleep(interval)」は指定された時間以上に待たされる場合があるようなので、
                            // 1回のループは「プログラムの処理時間+sleep(interval+α)」 となる。
                        } catch (InterruptedException e) {
                            // e.printStackTrace( );
                            if (DEBUG) Log.i(tag, "RunningDriver:run( ):InterruptedException;");
                            lEngineSW = false; // true; //
                        }
                    }
                }
            }
            // ???
            if( oRunnerBlocks.lDestroy ){
// Thread から Toast を表示する場合の定型処理。
                runOnUiThread(new Runnable() {
                    public void run() {
// (デフォルトでは Toast は画面の下側に表示されるが) Toast を画面の中央に表示させる。
// Toast の表示時間は、LENGTH_SHORT は2秒間、LENGTH_LONG は4秒間表示されるらしいので、
// 意図的に LENGTH_LONG を指定し、4秒間表示させている。
                        Toast toast = Toast.makeText(oMainApp, "GAME OVER", Toast.LENGTH_LONG);
                        toast.setGravity(Gravity.CENTER, 0, 0);
                        toast.show();
                    }
                });
            }
            oEngine = null;
        }

    }

    public class RunningSurfaceView extends RunningDriver {
        // volatile:最適化の抑制.
        volatile Canvas oCanvas;
        volatile SurfaceHolder oSurfaceHolder;
        volatile Bitmap oBOffScreenBuffer;
        volatile Canvas oCOffScreen;
        volatile Paint oPBackground = new Paint();
        volatile Paint oPWall = new Paint();
        // Paint oPOffScreen = new Paint();
        // Paint oPBlack = new Paint();

        // boolean lLaunchActivated = false; // true; //
        // Manufacturing
        int iScreenManufactWidth = 480;
        int iScreenManufactHeight = 590;
        // Physical
        volatile int iScreenPhysWidth, iScreenPhysHeight;
        // volatile int iScreenBiasX, iScreenBiasY;
        volatile double vdScreenRate, vdScreenRateMin, vdScreenRateMax;
        volatile int iScreenWidth, iScreenHeight;

        int iBlockDivideX = iWallSWQnt + 3;
        int iBlockDivideY = iWallSHQnt;
        int iBlockDistance;
        int iStockScreenBiasX, iStockScreenBiasY;
        int iStockSWQnt = 2;
        int iStockSHQnt ;
        int [][] d2iBlockPoolColorARGB;
        int [] d1iWaitingWeightIndex;
        int [] d1iDeviationPatternIndex = null;
        double vdDeviationPatternAdvent;
        RunnerBlocks[] d1oPatternIndex;

        public RunningSurfaceView( ) {
            super( );
            // black // white
            oPBackground.setColor(ContextCompat.getColor(oAppContext, R.color.white));
            oPBackground.setStyle(Paint.Style.FILL);
            oPWall.setColor(ContextCompat.getColor(oAppContext, R.color.brass));
            oPWall.setStyle(Paint.Style.FILL);
        }

        public void SurfaceCreate( ){
            lLaunchActivated = false; // true; //
            SurfaceView oSV = (SurfaceView) findViewById(R.id.surfaceview);
            SurfaceHolder oSH = oSV.getHolder();
            oSH.addCallback(oMainApp);
            oSurfaceHolder = oSH;
        }

        public void ScreenSetting(int width, int height) {
            int iBlockDistanceX, iBlockDistanceY;
            iScreenPhysWidth = width;
            iScreenPhysHeight = height;
            iScreenWidth = iScreenPhysWidth;
            iScreenHeight = iScreenPhysHeight;
            double vdSWRate = (double)iScreenWidth/iScreenManufactWidth;
            double vdSHRate = (double)iScreenHeight/iScreenManufactHeight;
            vdScreenRateMin = Math.min(vdSWRate, vdSHRate);
            vdScreenRateMax = Math.max(vdSWRate, vdSHRate);
// 開発時の解像度「iScreenManufactWidth、iScreenManufactHeight」と
// 実機の解像度「iScreenWidth、iScreenHeight」の違いを「vdScreenRate」で表わしている。
// 大抵は「vdScreenRate = vdScreenRateMin;」で良いと思われる。
// ただし、これでは対応できない場合もある。
            vdScreenRate = vdScreenRateMin;
            if(DEBUG)Log.i(tag,
                    "surfaceChanged( );"+"\n"+
                            "iScreenPhysWidth="+iScreenPhysWidth+";\n"+
                            "iScreenPhysHeight="+iScreenHeight+";\n"+
                            "iScreenWidth="+iScreenWidth+";\n"+
                            "iScreenHeight="+iScreenHeight+";\n"+
                            "vdScreenRateMin="+vdScreenRateMin+";\n"+
                            "vdScreenRateMax="+vdScreenRateMax+";\n"+
                            "vdScreenRate="+vdScreenRate+";\n"+
                            "");
            OffScreenCreate( );
        }

        public void OffScreenCreate( ) {
            oBOffScreenBuffer = Bitmap.createBitmap(
                    iScreenPhysWidth, iScreenPhysHeight,
                    Bitmap.Config.ARGB_8888);
            oCOffScreen = new Canvas(oBOffScreenBuffer);
        }

        @Override
        public void Initialize( ) {
            super.Initialize( );
            int iBlockDistanceX, iBlockDistanceY, iBiasX;

            iWallSHQnt = (iWallSWQnt + 3) * 19 / (11 + 3);
            int iBlockDivideX = iWallSWQnt + 3;
            int iBlockDivideY = iWallSHQnt;
            iBlockDistanceX = iScreenPhysWidth / iBlockDivideX;
            iBlockDistanceY = iScreenPhysHeight / iBlockDivideY;
            iBlockDistance = Math.min(iBlockDistanceX, iBlockDistanceY);
            // iBiasX = (iScreenPhysWidth - iBlockDivideX * iBlockDistance) / 2;
            iWallScreenBiasX = (iScreenPhysWidth - iBlockDivideX * (iBlockDistance - 1)) / 2;
            iWallScreenBiasY = iScreenPhysHeight - iBlockDivideY * iBlockDistance;
            iStockScreenBiasX = iWallScreenBiasX + iWallSWQnt * iBlockDistance + iBlockDistance / 2;
            iStockScreenBiasY = iWallScreenBiasY;
            iStockSHQnt = iWallSHQnt;
            dl1oRunnerBlocksDepot = new ArrayList<RunnerBlocks>( );

            vdDeviationPatternAdvent = 0;
            d1iDeviationPatternIndex = null;
            if( 13==iWallSWQnt ){
                vdDeviationPatternAdvent = .12d; // 12%
                d1iDeviationPatternIndex = new int [] {
                        3, 5, 3, 5,
                        3, 5, 3, 5,
                        3, 5, 3, 5,
                };
            }else if( 14==iWallSWQnt ){
                vdDeviationPatternAdvent = .18d; // 18%
                d1iDeviationPatternIndex = new int [] {
                        0, 3, 5,
                        0, 3, 5,
                        0, 3, 5,
                        0, 3, 5,
                };
            }
// 下記の for の条件を「i<d3sTetrazBlockColorRID.length」にすれば、
// 「oRunnerBlocks、 dl1oRunnerBlocksStock」において同じパターンのブロックは出現しない。
// 下記の for の条件が「i<d3sTetrazBlockColorRID.length * 2」の場合は、
// 「oRunnerBlocks、 dl1oRunnerBlocksStock」において同じパターンのブロックは2つまで出現する。
            // d1iDeviationPatternIndex = null;
            d1iWaitingWeightIndex = null;
            int n = 2;
            d1oPatternIndex = new RunnerBlocks[d3sTetrazBlockColorRID.length * n];
            for(int i = 0; i<d3sTetrazBlockColorRID.length * n; i++) {
                d1oPatternIndex[i] = new RunnerBlocks(
                        this, i % d3sTetrazBlockColorRID.length, -1);
                dl1oRunnerBlocksDepot.add(d1oPatternIndex[i]);
            }
            oRunnerBlocks = oBlockDepotDelivery(0);
            dl1oRunnerBlocksStock = new ArrayList<RunnerBlocks>( );
            for(int i = 0; i<4; i++) {
                dl1oRunnerBlocksStock.add(oBlockDepotDelivery(-1));
            }
            d1iWaitingWeightIndex = new int[(int)Math.pow(2, dl1oRunnerBlocksDepot.size()) - 1];
            if(DEBUG)Log.i(tag, "Initialize( ) : "+
                    "dl1oRunnerBlocksDepot.size( )="+dl1oRunnerBlocksDepot.size()+"; "+
                    "d1iWaitingWeightIndex.lengt="+d1iWaitingWeightIndex.length+"; "+
                    "");
            int l = 0;
            for(int i = 0; i<dl1oRunnerBlocksDepot.size(); i++) {
                int k = dl1oRunnerBlocksDepot.size() - 1 - i;
                if(DEBUG)Log.i(tag, "Initialize( ) : "+
                        "l="+l+"; "+
                        "i="+i+"; "+
                        "k="+k+"; "+
                        "");
                for(int j = 0; j<(int)Math.pow(2, i); j++) {
                    d1iWaitingWeightIndex[l] = k;
                    l++;
                }
            }
            BlockPoolBufClear( );
        }

        public RunnerBlocks oBlockDepotDelivery(int iCBR){
            RunnerBlocks oRB = null;
            double vdRandom;
            int iPattern;
            int iRandom = 0;
            int iIndex = -1;
            // ???
            if( null!=d1iDeviationPatternIndex &&
                    vdDeviationPatternAdvent>Math.random() ){
// 意図的に図形の出現率に少し偏りを出している。
                iRandom = (int)Math.floor(
                        Math.random() * d1iDeviationPatternIndex.length );
                iPattern = d1iDeviationPatternIndex[iRandom];
                if(DEBUG)Log.i(tag, "oBlockDepotDelivery( ) : "+
                        "d3sTetrazBlockColorRID.length="+d3sTetrazBlockColorRID.length+"; "+
                        "d1oPatternIndex.length="+d1oPatternIndex.length+"; "+
                        "iRandom="+iRandom+"; "+
                        "iPattern="+iPattern+"; "+
                        "");
                int i = iPattern;
                while( i<d1oPatternIndex.length && 0>iIndex ){
                    iIndex = dl1oRunnerBlocksDepot.indexOf(d1oPatternIndex[i]);
                    i = i + d3sTetrazBlockColorRID.length;
                }
                if(DEBUG)Log.i(tag, "oBlockDepotDelivery( ) : "+
                        "iIndex="+iIndex+"; "+
                        "");
            }
            if(0>iIndex){
                if( null==d1iWaitingWeightIndex ){
                    iRandom = (int)Math.floor(
                            Math.random() * dl1oRunnerBlocksDepot.size( ) );
                    iIndex = iRandom;
                }else{
// 普通に Random だけだと特定の図形が全然出て来ない場合があるので、
// d1iWaitingWeightIndex によって待っている順番(時間)に応じ
// (長く待ってるほど)選ばれる確率が上がるようにしている。
                    iRandom = (int)Math.floor(
                            Math.random() * d1iWaitingWeightIndex.length );
                    iIndex = d1iWaitingWeightIndex[iRandom];
                }
            }
            if(DEBUG)Log.i(tag, "oBlockDepotDelivery( ) : "+
                    "iWallSWQnt="+iWallSWQnt+"; "+
                    "iRandom="+iRandom+"; "+
                    "iIndex="+iIndex+"; "+
                    "");
            oRB = dl1oRunnerBlocksDepot.get(iIndex);
            oRB.Initialize(iCBR);
            dl1oRunnerBlocksDepot.remove(iIndex);
            return oRB;
        }

        public void BlockPoolBufClear( ){
            d2iBlockPoolColorARGB = new int[iWallSWQnt][iWallSHQnt];
            for( int iSIY = 0; iSIY<iWallSHQnt; iSIY++) {
                for( int iSIX = 0; iSIX<iWallSWQnt; iSIX++) {
                    d2iBlockPoolColorARGB[iSIX][iSIY] = oPWall.getColor( );
                }
            }
        }

        public void BlockStockDisplay( ){
            for( int iSIY = 0; iSIY<iStockSHQnt; iSIY++) {
                for( int iSIX = 0; iSIX<iStockSWQnt; iSIX++) {
                    oRunnerBlocks.BlockBullet(
                            iStockScreenBiasX, iStockScreenBiasY, iSIX, iSIY, oPWall);
                }
            }
            if( ! lVirginGround ) {
                int h, j;
                for (int i = 0; i < dl1oRunnerBlocksStock.size(); i++) {
                    RunnerBlocks oRBS = dl1oRunnerBlocksStock.get(i);
                    // ???
                    oRBS.GetBlockArea(oRBS.iCBlockPattern, oRBS.iCBlockRoll);
                    h = - oRBS.iBlocksAreaSHMin + oRBS.iBlocksAreaSHMax + 1;
                    oRBS.iSCY = iStockSHQnt - 1 - (i * 5) - oRBS.iBlocksAreaSHMax
                            - (int)Math.ceil( (double) (4 - h) / 2 ) ;
                    if(DEBUG)Log.i(tag, "BlockStockDisplay( ) : "+
                            "i="+i+"; "+
                            "h="+h+"; "+
                            "(4-h)/2="+((4 - h) / 2)+"; "+
                            "");
                    oRBS.iSCX = - oRBS.iBlocksAreaSWMin;
                    oRBS.FigureDraw(iStockScreenBiasX, iStockScreenBiasY,
                            iStockSWQnt, iStockSHQnt,
                            oRBS.iCBlockPattern, oRBS.iSCX, oRBS.iSCY, oRBS.iCBlockRoll,
                            TetrazBlocks_Enum.feDraw);
                }
            }
        }

        @Override
        public void RunningHeader() {
            // oPBackground.setColor(ContextCompat.getColor(oAppContext, R.color.sienna));
            // oPBackground.setStyle(Paint.Style.FILL);
            Paint oP = new Paint( );
            oP.setStyle(Paint.Style.FILL);
            oCanvas = oSurfaceHolder.lockCanvas();
// 実は Canvas は描画時のチラツキ防止のために内部的に OffScreen Buffer を持っているのだが、
// この Canvas の Double Buffering が返ってアダとなる場合があり、
// その場合は自分で OffScreen Buffer を用意する必要がある。
            if(lClearSW) {
                // ???
                if( ! lVirginGround ) {
                    lClearSW = false; // true; //
                }
                oCOffScreen.drawRect(
                        0, 0, iScreenPhysWidth, iScreenPhysHeight,
                        oPBackground); // oPWall // oPBackground
                // d2iBlockPoolColorARGB = new int[iWallSWQnt][iWallSHQnt];
                for( int iSIY = 0; iSIY<iWallSHQnt; iSIY++) {
                    for( int iSIX = 0; iSIX<iWallSWQnt; iSIX++) {
                        oP.setColor(d2iBlockPoolColorARGB[iSIX][iSIY]);
                        oRunnerBlocks.BlockBullet(
                                iWallScreenBiasX, iWallScreenBiasY, iSIX, iSIY, oP);
                    }
                }
                BlockStockDisplay( );
            }
        }

        @Override
        public void RunningFooter() {
            // Debug
            Paint oP = new Paint( );
            oP.setStyle(Paint.Style.FILL);
            if( lRepaintSW ) {
                // ???
                lRepaintSW = false; // true; //
                for (int iSIY = 0; iSIY < iWallSHQnt; iSIY++) {
                    for (int iSIX = 0; iSIX < iWallSWQnt; iSIX++) {
                        oP.setColor(d2iBlockPoolColorARGB[iSIX][iSIY]);
                        oRunnerBlocks.BlockBullet(
                                iWallScreenBiasX, iWallScreenBiasY, iSIX, iSIY, oP);
                    }
                }
                BlockStockDisplay( );
            }
            // Bitmap OffScreen Buffer を描画する
            oCanvas.drawBitmap(oBOffScreenBuffer, 0, 0, null);
            oSurfaceHolder.unlockCanvasAndPost(oCanvas);
        }

        @Override
        public boolean lBlockPoolBufSqueeze( TetrazBlocks_Enum oTB ){
            boolean lSqueeze = false; // true; //
            boolean lSqueezeLine = true; // false; //
            Paint oP = new Paint( );
            for( int iSIY = iWallSHQnt - 1; 0<=iSIY; iSIY--) {
                lSqueezeLine = true; // false; //
                for (int iSIX = 0; iSIX < iWallSWQnt; iSIX++) {
                    if ( oPWall.getColor( )==d2iBlockPoolColorARGB[iSIX][iSIY] ) {
                        lSqueezeLine = false; // true; //
                        break;
                    }
                }
                lSqueeze = lSqueeze | lSqueezeLine;
                if( lSqueeze ){
                    lRepaintSW = true; // false; //
                    if( oTB.feSqueeze==oTB ){
                        for (int iSIX = 0; iSIX < iWallSWQnt; iSIX++) {
                            int iSLY = iSIY - 1;
                            if( 0<=iSLY ){
                                d2iBlockPoolColorARGB[iSIX][iSIY]
                                        = d2iBlockPoolColorARGB[iSIX][iSLY];
                            }else{
                                d2iBlockPoolColorARGB[iSIX][iSIY] = oPWall.getColor( );
                            }
                        }
                    }
                }
            }
            return lSqueeze;
        }

    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(DEBUG)Log.i(tag, "Activity.onCreate( );");
        setContentView(R.layout.activity_main);
        oMainApp = this;
// System 共通の Context:別の Application とやりとりするとき。
        oBaseContext = getBaseContext( );
// Application 固有の Context(Application ごとに Context が変化する):Application 共通のモノが対象。
        oAppContext = getApplicationContext( );
// Activity 固有の Context(Activity ごとに Context が変化する):Activity に依存モノが対象。
        oContext = this;

        vsPackageName = getPackageName( );
        try {
            PackageInfo packageInfo = this.getPackageManager( ).getPackageInfo(
                    this.getPackageName( ), PackageManager.GET_META_DATA );
            // 「<Project名>→app→build.gradle」ファイル内の
            // 「versionName、versionCode」の設定値を取得。
            vsVersionName = packageInfo.versionName;
            iVersionCode = packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            // 例外処理
            e.printStackTrace( );
        }

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        oActionBar = getSupportActionBar();

        // oActionBar.setIcon(R.drawable.ic_menu_info_details);
        oActionBar.setLogo(R.drawable.x_logo_android022);
        // ↑ 実際は Logo マークはアプリ固有の Icon にすべきモノです。
        // アプリ固有の Logo マークと言われてもイメージが湧かない方は
        // ブラウザの Icon(Logo マーク)をイメージしてみて下さい。
        // 下記サイトに主要ブラウザの Icon(Logo マーク)が掲載されています。
        // http://freesoft-100.com/pasokon/browser.html
        // ちなみに Toolbar に表示する Icon サイズのスタンダードは
        // "drawable-mdpi"フォルダー用は「32x32」、
        // "drawable-hdpi"フォルダー用は「48x48」だと思われます。

        vsToolbarTitle = "  "+getString(R.string.app_name)+
                fsDebugCrest+
                " V"+vsVersionName+ // Version.
                " R"+String.valueOf(iVersionCode); // Revision.
// ↑ Toolbar に Logo マークを表示させるとタイトルと Logo マークとの間に隙間がないので、
// タイトルの先頭に半角スペースを2つ入れている。
        oActionBar.setTitle(vsToolbarTitle);

        eAppLaunchMode = AppLaunchMode_Enum.feCreate;
        oRunningSurfaceView = new RunningSurfaceView();
// 「MainActivity」クラスの この「onCreate( )」コンストラクターでは
// まだ"SurfaceView"の生成が完了してない場合が有るようで、
// その時点で"Thread"が実行された場合
// 「oSurfaceHolder.lockCanvas( )」による"Canvas"の生成が"null"になってしまうので、
// 「RunningSurfaceView( )」コンストラクターには
// 「EngineCreate( )」("Thread"の生成・実行)は内包しない。
// 又、「RunnerCircle( )」 コンストラクター実行時には
// 「iScreenWidth、iScreenHeight」が設定されていなければならないので
// 「RunningSurfaceView( )」コンストラクターには内包せず、別途「Initialize( )」に内包する。
// それら「Initialize( )、EngineCreate( )」は「launch( )」に内包し「surfaceChanged( )」で実行する。
// ただし「launch( )」に内包された それら(「Initialize( )、EngineCreate( )」)は"appLaunchMode"で実行の可否が決定される。
        if (DEBUG) Log.i(tag, "oRunningSurfaceView = new RunningSurfaceView( );");

        for (int iWId : ONTOUCH_WIDGETS) {
            findViewById(iWId).setOnTouchListener(this);
        }
        cibBlockRight = (ImageButton)findViewById(R.id.cibBlockRight);
        cibBlockLeft = (ImageButton)findViewById(R.id.cibBlockLeft);
        cibBlockDown = (ImageButton)findViewById(R.id.cibBlockDown);
    }

    @Override
    protected void onRestart( ) {
        super.onRestart();
        if(DEBUG)Log.i(tag, "Activity.onRestart( );");

        eAppLaunchMode = AppLaunchMode_Enum.feRestart;
    }

    @Override
    protected void onStart( ) {
        super.onStart();
        if(DEBUG)Log.i(tag, "Activity.onStart( );");

        oRunningSurfaceView.SurfaceCreate( );
    }

    @Override
    protected void onResume( ) {
        super.onResume();
        if(DEBUG)Log.i(tag, "Activity.onResume( );");
    }

    @Override
    protected void onPause( ) {
        super.onPause();
        if(DEBUG)Log.i(tag, "Activity.onPause( );");
    }

    @Override
    protected void onStop() {
        super.onStop();
        if(DEBUG)Log.i(tag, "Activity.onStop( );");

        oRunningSurfaceView.EngineDestroy( );
        // oRunningSurfaceView.oSurfaceHolder.removeCallback(oMainApp);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(DEBUG)Log.i(tag, "Activity.onDestroy( );");
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // super.onTouch(v, event);
        if(DEBUG) Log.i(tag,"Activity.onTouch( );");

        int action = event.getAction();
        switch(v.getId()) {
            case R.id.cibBlockRotate :
                if (DEBUG) Log.i(tag, "R.id.cibBlockRotate");
                if (action == MotionEvent.ACTION_DOWN) {
                    if(DEBUG) Log.i(tag,"MotionEvent.ACTION_DOWN;");
                    oRunningSurfaceView.oRunnerBlocks.lEBlockRoll = true; // false; //
                }
                if ((action == MotionEvent.ACTION_UP)) {
                    if(DEBUG) Log.i(tag,"MotionEvent.ACTION_UP;");

                }
                break;

            case R.id.cibBlockRight :
                if (DEBUG) Log.i(tag, "R.id.cibBlockRight");
                if (action == MotionEvent.ACTION_DOWN) {
                    if(DEBUG) Log.i(tag,"MotionEvent.ACTION_DOWN;");
                    oRunningSurfaceView.iETouchAction_BlockRightPress = MotionEvent.ACTION_DOWN;
                }
                if ((action == MotionEvent.ACTION_UP)) {
                    if(DEBUG) Log.i(tag,"MotionEvent.ACTION_UP;");
                    oRunningSurfaceView.iETouchAction_BlockRightRelease = MotionEvent.ACTION_UP;
                }
// 意図的に戻り値に false を返し、他の Event Listener の処理を継続させている。
                return false; // true; //
            // break;

            case R.id.cibBlockLeft :
                if (DEBUG) Log.i(tag, "R.id.cibBlockLeft");
                if (action == MotionEvent.ACTION_DOWN) {
                    if(DEBUG) Log.i(tag,"MotionEvent.ACTION_DOWN;");
                    oRunningSurfaceView.iETouchAction_BlockLeftPress = MotionEvent.ACTION_DOWN;
                }
                if ((action == MotionEvent.ACTION_UP)) {
                    if(DEBUG) Log.i(tag,"MotionEvent.ACTION_UP;");
                    oRunningSurfaceView.iETouchAction_BlockLeftRelease = MotionEvent.ACTION_UP;
                }
// 意図的に戻り値に false を返し、他の Event Listener の処理を継続させている。
                return false; // true; //
            // break;

            case R.id.cibBlockDown :
                if (DEBUG) Log.i(tag, "R.id.cibBlockDown");
                if (action == MotionEvent.ACTION_DOWN) {
                    if(DEBUG) Log.i(tag,"MotionEvent.ACTION_DOWN;");
                    oRunningSurfaceView.iETouchAction_BlockDown = MotionEvent.ACTION_DOWN;
                }
                if ((action == MotionEvent.ACTION_UP)) {
                    if(DEBUG) Log.i(tag,"MotionEvent.ACTION_UP;");

                }
// 意図的に戻り値に false を返し、他の Event Listener の処理を継続させている。
                return false; // true; //
            // break;

            default :
                if (DEBUG) Log.i(tag, "Default");
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                return false; // true; //
        }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public void onClick(View v) {
        // super.onClick(v);
        if(DEBUG) Log.i(tag,"Activity.onClick( );");
        switch(v.getId()) {
            case R.id.cibBlockRotate :
                if (DEBUG) Log.i(tag, "R.id.cibBlockRotate");
                // oRunningSurfaceView.oRunnerBlocks.BlockRotate();
                break;

            default :
                if (DEBUG) Log.i(tag, "Default");
                return;
        }
        return;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        // アクションバー内で使用する為のメニューアイテムを実体化
        MenuInflater inflater = getMenuInflater();
        // MenuInflater inflater = getSupportMenuInflater();
        inflater.inflate(R.menu.main, menu);
        // 「R.menu.main」の「menu.main」は 恐らく「res」からの「フォルダー名+ファイル名」
        oMainMenu = menu;
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        if(DEBUG) Log.i(tag,"Activity.onOptionsItemSelected( );");
        switch(item.getItemId()) {
            case R.id.menu_main_settings :
                if (DEBUG) Log.i(tag, "menu_main_settings");
                oRunningSurfaceView.RunnerWait(true); // false;
                break;

            case R.id.menu_main_settings_play :
                if (DEBUG) Log.i(tag, "menu_main_settings_play");
                if( oRunningSurfaceView.lEngineSW ) {
                    oRunningSurfaceView.RunnerWait(false); // true;
                }else{
                    oRunningSurfaceView.Initialize();
                    oRunningSurfaceView.EngineCreate(false); // true; //
                }
                break;

            case R.id.menu_main_settings_wait :
                if (DEBUG) Log.i(tag, "menu_main_settings_wait");
                // oRunningSurfaceView.RunnerWait(true); // false;
                break;

            case R.id.menu_main_settings_renew_impossible :
                if (DEBUG) Log.i(tag, "menu_main_settings_renew_impossible");
                oRunningSurfaceView.iWallSWQnt = 11;
                // oRunningSurfaceView.iWallSHQnt = 19;
                oRunningSurfaceView.Initialize();
                oRunningSurfaceView.EngineCreate(false); // true; //
                break;

            case R.id.menu_main_settings_renew_tight :
                if (DEBUG) Log.i(tag, "menu_main_settings_renew_tight");
                oRunningSurfaceView.iWallSWQnt = 12;
                oRunningSurfaceView.Initialize();
                oRunningSurfaceView.EngineCreate(false); // true; //
                break;

            case R.id.menu_main_settings_renew_medium :
                if (DEBUG) Log.i(tag, "menu_main_settings_renew_medium");
                oRunningSurfaceView.iWallSWQnt = 13;
                oRunningSurfaceView.Initialize();
                oRunningSurfaceView.EngineCreate(false); // true; //
                break;

            case R.id.menu_main_settings_renew_easy :
                if (DEBUG) Log.i(tag, "menu_main_settings_renew_easy");
                oRunningSurfaceView.iWallSWQnt = 14;
                oRunningSurfaceView.Initialize();
                oRunningSurfaceView.EngineCreate(false); // true; //
                break;

            case R.id.menu_main_settings_exit :
                if (DEBUG) Log.i(tag, "menu_main_settings_exit");
                // ???
                oMainApp.finish( );
                break;

            default :
                if (DEBUG) Log.i(tag, "Default");
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                return false; // true; //
        }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        // super.dispatchKeyEvent(event);
        if(DEBUG) Log.i(tag,"Activity.dispatchKeyEvent( );");
        final int action = event.getAction();
        final int keyCode = event.getKeyCode();
        switch(keyCode) {
            case KeyEvent.KEYCODE_MENU :
                if(DEBUG) Log.i(tag,"KeyEvent.KEYCODE_MENU;");
                if ( action==KeyEvent.ACTION_UP ) {
                    if(DEBUG) Log.i(tag,"KeyEvent.ACTION_UP;");
                    if ( oMainMenu!=null ) {
                        if(DEBUG) Log.i(tag,"oMainMenu.performIdentifierAction( );");
// Toast の表示時間は、LENGTH_SHORT は2秒間、LENGTH_LONG は4秒間表示されるらしいので、
// LENGTH_SHORT の表示期間のに合わせて2秒間 Sleep させる。
                        oRunningSurfaceView.iESleepInterval = 2000; // 2000ms
// Super Class を呼び出さずに帰り値に true を返して Menu Key を無効化する。
                        // oMainMenu.performIdentifierAction(R.id.menu_main_settings, 0);
                        Toast.makeText(oMainApp, "Please Command this Application from a \"Three Dots\" Icon.", Toast.LENGTH_SHORT).show();
                    }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
                    return true; // false; //
                }
                break;

            case KeyEvent.KEYCODE_BACK :
                if(DEBUG) Log.i(tag,"KeyEvent.KEYCODE_BACK;");
                if ( action==KeyEvent.ACTION_UP ) {
                    if(DEBUG) Log.i(tag,"KeyEvent.ACTION_UP;");
// Toast の表示時間は、LENGTH_SHORT は2秒間、LENGTH_LONG は4秒間表示されるらしいので、
// LENGTH_SHORT の表示期間のに合わせて2秒間 Sleep させる。
                    oRunningSurfaceView.iESleepInterval = 2000; // 2000ms
// Super Class を呼び出さずに帰り値に true を返して Back Key を無効化する。
                    Toast.makeText(oMainApp, "Please Command this Application from a \"Three Dots\" Icon.", Toast.LENGTH_SHORT).show();
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
                    return true; // false; //
                }
                break;

            default :
                super.dispatchKeyEvent(event);
                if (DEBUG) Log.i(tag, "Default");

                break;
        }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
        return false; // true; //
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) { }

    @Override
    public void surfaceChanged(SurfaceHolder holder,
        int format, int width, int height) {
        RunningSurfaceView oRSV = oRunningSurfaceView;
        oRSV.ScreenSetting(width, height);
        if( ! oRSV.lLaunchActivated ){
// lLaunchActivated がクリアーされない限りは、「surfaceChanged( )」 が2回以上呼ばれても、ここは1回しか実行されない。
            oRSV.Launch(eAppLaunchMode);
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) { }

}



『<Project名>→app→main→res→layout→activity_main.xml』


<?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:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    tools:context="com.example.surfaceview.MainActivity" >

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
        android:minHeight="?attr/actionBarSize"
        android:background="#9b7fff"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <SurfaceView
        android:id="@+id/surfaceview"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="7dp"
        android:layout_marginLeft="7dp"
        android:layout_marginRight="7dp"
        android:layout_marginBottom="0dp"
        android:weightSum="1">

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/cibBlockLeft"
            android:src="@drawable/xic_menu_reverse_clip"/>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="0.5666"
            android:gravity="center_horizontal">

            <ImageButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/cibBlockDown"
                android:src="@drawable/xic_menu_down_clip"/>

        </LinearLayout>

        <ImageButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/cibBlockRight"
            android:src="@drawable/ic_menu_play_clip"/>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="0.4334"
            android:gravity="right">

            <ImageButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/cibBlockRotate"
                android:src="@drawable/ic_menu_rotate"/>
        </LinearLayout>

    </LinearLayout>

</LinearLayout>



『<Project名>→app→src→main→res→menu→main.xml(フォルダーとファイルを作成)』


<?xml version="1.0" encoding="utf-8"?>
<menu
    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"
    tools:context="com.example.tetraz.MainActivity">

    <item android:id="@+id/menu_main_settings"
        android:title="Settings"
        android:icon="@drawable/ic_menu_moreoverflow_normal_holo_light"
        app:showAsAction="always">
        <!-- ↑ showAsAction:Toolbar に対する表示設定。 -->
        <!-- "always":常に表示、"never":常に表示しない、"ifRoom":表示する余裕があれば表示。 -->
        <menu>
            <item android:id="@+id/menu_main_settings_play"
                android:title="Play">
            </item>

            <item android:id="@+id/menu_main_settings_wait"
                android:title="Wait">
            </item>

            <item android:id="@+id/menu_main_settings_renew_impossible"
                android:title="Renew Impossible">
            </item>

            <item android:id="@+id/menu_main_settings_renew_tight"
                android:title="Renew Tight">
            </item>

            <item android:id="@+id/menu_main_settings_renew_medium"
                android:title="Renew Medium">
            </item>

            <item android:id="@+id/menu_main_settings_renew_easy"
                android:title="Renew Easy">
            </item>

            <item android:id="@+id/menu_main_settings_exit"
                android:title="Exit">
            </item>
        </menu>
    </item>

</menu>



『<Project名>→app→src→main→res→values→colors.xml』


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>

    <!-- 文字列(16進値)の4バイト指定時の場合は(最上位の)4バイト目が不透明度の指定になるようだ。 -->
    <color name="empty">#00000000</color>
    <color name="black">#000000</color>
    <color name="gray">#808080</color>
    <color name="silver">#c0c0c0</color>
    <color name="white">#ffffff</color>
    <color name="maroon">#800000</color>
    <color name="red">#ff0000</color>
    <color name="purple">#800080</color>
    <color name="fuchsia">#ff00ff</color>
    <color name="magenta">#ff00ff</color>
    <color name="green">#008000</color>
    <color name="lime">#00ff00</color>
    <color name="olive">#808000</color>
    <color name="yellow">#ffff00</color>
    <color name="navy">#000080</color>
    <color name="blue">#0000ff</color>
    <color name="teal">#008080</color>
    <color name="aqua">#00ffff</color>
    <color name="cyan">#00ffff</color>

    <color name="orange">#ffa500</color>
    <color name="pink">#ffc0cb</color>
    <color name="violet">#ee82ee</color>
    <color name="hotpink">#ff69b4</color>
    <color name="blueviolet">#8a2be2</color>
    <color name="darkviolet">#9400d3</color>
    <color name="greenyellow">#adff2f</color>
    <color name="lawngreen">#7cfc00</color>
    <color name="yellowgreen">#9acd32</color>

    <color name="lightgrey">#d3d3d3</color>
    <color name="darkgray">#a9a9a9</color>
    <color name="lightslategray">#778899</color>
    <color name="slategray">#708090</color>
    <color name="dimgray">#696969</color>

    <color name="khaki">#f0e68c</color>
    <color name="tan">#d2b48c</color>
    <color name="darkkhaki">#bdb76b</color>
    <color name="brass">#b5a642</color>
    <color name="darkgoldenrod">#b8860b</color>
    <color name="peru">#cd853f</color>
    <color name="goldenrod">#daa520</color>
    <color name="chocolate">#d2691e</color>
    <color name="sienna">#a0522d</color>
    <color name="saddlebrown">#8b4513</color>
    <color name="darkred">#8b0000</color>
    <color name="brown">#a52a2a</color>
    <color name="firebrick">#b22222</color>
    <color name="darkbrown">#da0b00</color>
    <color name="orangered">#ff4500</color>
</resources>



『<Project名>→app→src→main→res→values→styles.xml』


<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>



『<Project名>→app→main→AndroidManifest.xml』


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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <!-- screenOrientation="portrait":縦表示・固定 -->
        <activity android:name="com.example.tetraz.MainActivity"
            android:screenOrientation="portrait"
            android:launchMode="singleInstance" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

</manifest>


<Number>: [000009D3]  <Date>: 2016/05/08 16:46:57
<Title>: AppCompat 012
<Name>: amanojaku@管理人



ActionBarActivity が非推奨になったので、チャントした(プログラム的に) Empty(空(から)) な AppCompatActivity を作ってみる。

以前は良くワカランかったが、「getSupportActionBar( )」で1歩前進したが、それでは Toolbar の「Logo(アイコン)」の表示ができなかったので、下記「OKWAVE」で「Logo(アイコン)、Icon」の表示方法を ご教授いただきました(詳細は下記「OKWAVE」ページ参照)。
Toolbar の Icon のイベントの取得は そんなに難しくないだろうと思って、イロイロ検索しても全然ダメでした。

OKWAVE
http://okwave.jp/qa/q9115191.html


《参考》

TextViewに枠線を付ける&背景色を変更する - yokkongの日記
http://d.hatena.ne.jp/yokkong/20111116/1321425474

【Android開発】スタイルにborderがないけど枠線を表示したい
http://se-suganuma.blogspot.jp/2010/02/androidborder.html


とりあえず、[New Project]で(1ページ目)[Application name]:「AppCompat」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。

『<Project名>→app→main→res→drawable-hdpi』フォルダー(存在しない場合は作成してやる)内に「Android SDK」のデフォルト・アイコン「ic_menu_moreoverflow_normal_holo_light.png」をコピーする。
一応、「アプリの Logo マーク」を Android のマスコット・キャラの「Droid君」にしている。
下の方に『<Project名>→app→main→res→drawable-hdpi』フォルダー用の画像ファイル「アプリの Logo マーク:「48x48」Pixel、その他"TextView"用アイコンをアップしてあります。
"drawable-mdpi"フォルダー用は手抜きして作ってないが、解像度が低い端末でも自動的に その端末の解像度に合うように縮小して表示される。
その場合、当然 画質は悪くなるので画質を綺麗にしたい場合は手抜きせずにチャント"drawable-mdpi"フォルダー用の画像ファイルを作成してやれば良い。
「Toolbar」の UI はスタンダードな「ActionBarActivity」のような感じになっている。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。


『<Project名>→app→main→java→com.example.appcompat→MainActivity(MainActivity.java)』


package com.example.appcompat;

import android.os.Bundle;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.KeyEvent;
import android.os.Environment;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.Context;
import android.content.res.Resources;
import android.content.Intent;
import android.app.PendingIntent;
import android.app.Activity;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
// import android.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.util.Log;

import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity
        implements View.OnClickListener {
    static final String fsDebugCrest = " 012";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    DisplayMetrics oDisplayMetrics;
    public static int iPhysicalWidth, iPhysicalHeight;
    float vfDisplayMetricsDensity;

    MainActivity oMainApp;
    Context oBaseContext;
    Context oAppContext; // Application Context.
    Context oContext; // Activity Context.
    String vsToolbarTitle;
    Menu oMainMenu;

    String vsPackageName;
    String vsVersionName;
    int iVersionCode;

    TextView wtvMessage;
    static final int[] WIDGETS =  new int[]{
            R.id.textView1, R.id.textView2,
            R.id.button1, R.id.checkBox1, R.id.toggleButton1, R.id.radioButton1 };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (DEBUG) Log.i(tag, "Activity.onCreate( );");
        setContentView(R.layout.activity_main);
        oMainApp = this;
// System 共通の Context:別の Application とやりとりするとき。
        oBaseContext = getBaseContext();
// Application 固有の Context(Application ごとに Context が変化する):Application 共通のモノが対象。
        oAppContext = getApplicationContext();
// Activity 固有の Context(Activity ごとに Context が変化する):Activity に依存モノが対象。
        oContext = this;
        oDisplayMetrics = oAppContext.getResources( ).getDisplayMetrics( );
        vfDisplayMetricsDensity = oDisplayMetrics.density;
        iPhysicalWidth = oDisplayMetrics.widthPixels;
        iPhysicalHeight = oDisplayMetrics.heightPixels;
        if (DEBUG) Log.i(tag, "Activity.onCreate( ) : " +
                "vfDisplayMetricsDensity=" + vfDisplayMetricsDensity + "; " +
                "iPhysicalWidth=" + iPhysicalWidth + "; " +
                "iPhysicalHeight=" + iPhysicalHeight + "; " +
                "");

        vsPackageName = getPackageName();
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(
                    getPackageName(), PackageManager.GET_META_DATA);
            // 「<Project名>→app→build.gradle」ファイル内の
            // 「versionName、versionCode」の設定値を取得。
            vsVersionName = packageInfo.versionName;
            iVersionCode = packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }

        wtvMessage = (TextView) findViewById(R.id.wtvMessage);
        wtvMessage.append("fsDebugCrest=" + fsDebugCrest + ";\n" +
                "PackageName=" + vsPackageName + ";\n" +
                "VersionName=" + vsVersionName + ";\n" +
                "RevisionNo=" + String.valueOf(iVersionCode) + ";\n" +
                "vfDisplayMetricsDensity=" + vfDisplayMetricsDensity + ";\n" +
                "iPhysicalWidth=" + iPhysicalWidth + ";\n" +
                "iPhysicalHeight=" + iPhysicalHeight + "\n" +
                "");

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar actionbar = getSupportActionBar();

        // actionbar.setIcon(R.drawable.ic_menu_info_details);
        actionbar.setLogo(R.drawable.x_logo_android022);
        // ↑ 実際は Logo マークはアプリ固有の Icon にすべきモノです。
        // アプリ固有の Logo マークと言われてもイメージが湧かない方は
        // ブラウザの Icon(Logo マーク)をイメージしてみて下さい。
        // 下記サイトに主要ブラウザの Icon(Logo マーク)が掲載されています。
        // http://freesoft-100.com/pasokon/browser.html
        // ちなみに Toolbar に表示する Icon サイズのスタンダードは
        // "drawable-mdpi"フォルダー用は「32x32」、
        // "drawable-hdpi"フォルダー用は「48x48」だと思われます。

        vsToolbarTitle = "  " + getString(R.string.app_name) +
                fsDebugCrest +
                " V" + vsVersionName + // Version.
                " R" + String.valueOf(iVersionCode); // Revision.
// ↑ Toolbar に Logo マークを表示させるとタイトルと Logo マークとの間に隙間がないので、
// タイトルの先頭に半角スペースを2つ入れている。
        actionbar.setTitle(vsToolbarTitle);

        for (int iWId : WIDGETS) {
            View wvw = findViewById(iWId);
            wvw.setOnClickListener(this);
        }
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        if (DEBUG) Log.i(tag, "Activity.onRestart( );");
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (DEBUG) Log.i(tag, "Activity.onStart( );");
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (DEBUG) Log.i(tag, "Activity.onResume( );");
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (DEBUG) Log.i(tag, "Activity.onPause( );");
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (DEBUG) Log.i(tag, "Activity.onStop( );");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (DEBUG) Log.i(tag, "Activity.onDestroy( );");
    }

    @Override
    public void onClick(View v) {
        // super.onClick(v);
        if(DEBUG) Log.i(tag,"Activity.onClick( );");
        switch(v.getId()) {
            case  R.id.textView1 :
                if (DEBUG) Log.i(tag, "textView1");

                break;
            case  R.id.textView2 :
                if (DEBUG) Log.i(tag, "textView2");

                break;
            case R.id.button1 :
                if (DEBUG) Log.i(tag, "button1");

                break;
            case R.id.checkBox1 :
                if (DEBUG) Log.i(tag, "checkBox1");

                break;
            case R.id.toggleButton1 :
                if (DEBUG) Log.i(tag, "toggleButton1");

                break;
            case R.id.radioButton1 :
                if (DEBUG) Log.i(tag, "radioButton1");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
                return;
        }
        return;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        // アクションバー内で使用する為のメニューアイテムを実体化
        MenuInflater inflater = getMenuInflater();
        // MenuInflater inflater = getSupportMenuInflater();
        inflater.inflate(R.menu.main, menu);
        // 「R.menu.main」の「menu.main」は 恐らく「res」からの「フォルダー名+ファイル名」
        oMainMenu = menu;
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        if(DEBUG) Log.i(tag,"Activity.onOptionsItemSelected( );");
        switch(item.getItemId()) {
            case R.id.menu_main_settings :
                if (DEBUG) Log.i(tag, "menu_main_settings");

                break;
            case R.id.menu_main_settings_item1 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item1");

                break;
            case R.id.menu_main_settings_item2 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item2");

                break;
            case R.id.menu_main_settings_item3 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item3");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                return false; // true; //
        }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        super.dispatchKeyEvent(event);
        if (DEBUG) Log.i(tag, "Activity.dispatchKeyEvent( );");
        final int action = event.getAction();
        final int keyCode = event.getKeyCode();
        switch (keyCode) {
            case KeyEvent.KEYCODE_MENU:
                if (DEBUG) Log.i(tag, "KeyEvent.KEYCODE_MENU;");
                if (action == KeyEvent.ACTION_UP) {
                    if (DEBUG) Log.i(tag, "KeyEvent.ACTION_UP;");
                    if (oMainMenu != null) {
                        if (DEBUG) Log.i(tag, "oMainMenu.performIdentifierAction( );");
                        oMainMenu.performIdentifierAction(R.id.menu_main_settings, 0);
                    }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
                    return true; // false; //
                }
                break;

            default:
                if (DEBUG) Log.i(tag, "KeyEvent:Default");

                break;
        }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
        return false; // true; //
    }

}



『<Project名>→app→main→res→drawable→border.xml』(ファイルを作成)
※「UI Widget」の枠として使用する。


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ffffff" />
    <padding android:top="3dp" android:bottom="3dp"
        android:left="7dp" android:right="7dp" />
    <stroke android:width="1px" android:color="#000000" />
    <corners android:radius="5dp" />
</shape>



『<Project名>→app→main→res→drawable→button_style.xml』(ファイルを作成)
※「TextView」をボタン・スタイルで表示させている。


<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/ButtonBackground"/>
            <corners android:radius="3dp" />
            <stroke android:width="2dp" android:color="@color/ButtonShadow" />
            <!-- 「stroke android:width=」は ずらす幅より少し大きな値にする。 -->
        </shape>
    </item>
    <item android:bottom="1.45dp" android:right="1.47dp">
        <shape android:shape="rectangle">
            <solid android:color="@color/ButtonBackground"/>
            <padding android:top="4.4dp" android:bottom="4.4dp"
                android:left="5dp" android:right="5dp" />
            <corners android:radius="3dp" />
        </shape>
    </item>
</layer-list>



『<Project名>→app→main→res→layout→activity_main.xml』


<?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"
    tools:context=".MainActivity"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/actionBarSize"
        android:background="@color/Background_Cornflowerblue" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_margin = "7dp"
        android:background="@drawable/border" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="top"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:id="@+id/wtvMessage" />

    </ScrollView>

<!-- ボタンは左右にムダなスペースがあるので、表示幅が Tight でボタンが表示できない場合、-->
<!-- TextView をボタン・スタイルで表示させれば良い。 -->
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:textColor="@color/black"
            android:layout_marginTop = "6dp"
            android:layout_marginBottom = "6dp"
            android:layout_marginLeft = "3.5dp"
            android:layout_marginRight = "3.5dp"
            android:background="@drawable/button_style"
            android:text="TextView1"
            android:id="@+id/textView1" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:textColor="@color/black"
            android:layout_marginTop = "6dp"
            android:layout_marginBottom = "6dp"
            android:layout_marginLeft = "3.5dp"
            android:layout_marginRight = "3.5dp"
            android:background="@drawable/button_style"
            android:drawableRight="@drawable/xic_menu_moreoverflow_focused_holo_light0272"
            android:text="TextView2"
            android:id="@+id/textView2" />
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:gravity="center">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:text="Button1"
            android:id="@+id/button1" />

        <ToggleButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:text="ToggleButton1"
            android:id="@+id/toggleButton1" />
    </LinearLayout>

    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="22dp"
        android:text="CheckBox1"
        android:id="@+id/checkBox1" />

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="22dp"
        android:layout_gravity="center_horizontal"
        android:text="RadioButton1"
        android:id="@+id/radioButton1" />

</LinearLayout>



『<Project名>→app→src→main→res→menu→main.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<menu
    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"
    tools:context=".MainActivity">

    <item android:id="@+id/menu_main_settings"
        android:title="Settings"
        android:icon="@drawable/ic_menu_moreoverflow_normal_holo_light"
        app:showAsAction="always">
        <!-- ↑ showAsAction:Toolbar に対する表示設定。 -->
        <!-- "always":常に表示、"never":常に表示しない、"ifRoom":表示する余裕があれば表示。 -->
        <menu>
            <item android:id="@+id/menu_main_settings_item1"
                android:title="item1">
            </item>

            <item android:id="@+id/menu_main_settings_item2"
                android:title="item2">
            </item>

            <item android:id="@+id/menu_main_settings_item3"
                android:title="item3">
            </item>
        </menu>
    </item>

</menu>



『<Project名>→app→src→main→res→values→colors.xml』


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>

    <color name="holo_blue_light">#33b5e5</color>
    <color name="Background_Cornflowerblue">#6495ED</color>
    <color name="Background_MediumslateblueLike">#9b7fff</color>

    <color name="ButtonBackground">#d7d7d7</color>
    <color name="ButtonShadow">#c6c6c6</color>
    <color name="GrayOutText001">#909090</color>
    <color name="GrayOutText002">#888888</color>
    <color name="GrayOutText003">#777777</color>
    <color name="SettingMenuBackground">#eeeeee</color>

    <color name="black">#000000</color>
    <color name="gray">#808080</color>
    <color name="silver">#c0c0c0</color>
    <color name="white">#ffffff</color>
    <color name="maroon">#800000</color>
    <color name="red">#ff0000</color>
    <color name="purple">#800080</color>
    <color name="fuchsia">#ff00ff</color>
    <color name="magenta">#ff00ff</color>
    <color name="green">#008000</color>
    <color name="lime">#00ff00</color>
    <color name="olive">#808000</color>
    <color name="yellow">#ffff00</color>
    <color name="navy">#000080</color>
    <color name="blue">#0000ff</color>
    <color name="teal">#008080</color>
    <color name="aqua">#00ffff</color>
    <color name="cyan">#00ffff</color>

    <color name="pink">#ffc0cb</color>
    <color name="orange">#ffa500</color>
    <color name="greenyellow">#adff2f</color>
    <color name="yellowgreen">#9acd32</color>
</resources>



『<Project名>→app→src→main→res→values→styles.xml』


<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>



『<Project名>→app→main→AndroidManifest.xml』


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

    <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:launchMode="singleInstance" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


<Number>: [000009E1]  <Date>: 2016/03/20 06:31:02
<Title>: WidgetELSProperty 009
<Name>: amanojaku@管理人



Widget(今回は Service 内)で View コンポーネントの Property を動的に変更する。
クリックにより(Service の) onStartCommand が実行されたら、TextView の Background Color を Random に変更する。
Widget のメソッド実行時に設定された(Widget Class 内の) Static 変数は、そのメソッドが実行中なら、当然 その値は保持されているだろうが、その後 その(Widget Class 内の) Static 変数の値が消失しているような感じなので、Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無いようだ、当然 Service 起動時に その(Widget Class 内の) Static 変数の値が残っている保障など無い。
よって Widget から Service へのデータの受け渡しには Wgeidt Class 内の Static 変数は使えない(Widget → Service の一方通行のデータの受け渡しに限定するなら、Intent により非常に簡単にデータを受け渡せるようだ、それに関しては次の WidgetILSToast にて記す)。
TextView のデフォルト値に Text として「A Widget isn't launched.」を設定しているので、Widget を画面に設置した時に「A Widget isn't launched.」と表示されたら、Widget が起動していない事を意味する。
しばらく待てば Widget が起動するハズだが、端末が起動中の場合やデバッグ時などは Widget が起動するまで やたら時間がかかる場合がある。
つまり、実際の Widget アプリを作成する場合も、Widget が起動してない事が判るように何かメッセージを表示するとか、(メッセージを表示できない場合は) Widget が未起動時のアイコンと起動時のアイコンを切り替えて表示するとかの工夫が必要になる場合もあるでしょう。

とりあえず、[New Project]で(1ページ目)[Application name]:「WidgetELSProperty」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。

targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を13以下にすれば良いらしい。
現状でインストールできる「API Level 13」以下の SDK Version の最大値は「API Level 10」(Android OS 2.3.3)となり、当方も「API Level 10」(Android OS 2.3.3)をインストールしている、よって targetSdkVersion の値は「10」としている。
なお、targetSdkVersion の設定方法は仕様が変更され「<プロジェクト名>→app→build.gradle」で設定しなければならないようだ(これらを「AndroidManifest.xml」で指定しても無視されるようだ)、「build.gradle」は同名のファイルが他にもあるようなので間違えないように注意が必要。
※必ず(Android Studio からではなく)オリジナルの「Android SDK Manager(SDK Manager.exe)」から「API Level 10」(Android OS 2.3.3)をインストールして下さい。
「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。


《参考》

Yukiの枝折: Android:Serviceの基本とonStartCommandの戻り値による ...
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html

Android Widgetのライフサイクルについて要点だけ | スマートフォン要点だけブログ
http://blog.imho.jp/2011/05/android-widget.html

Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ)
http://techbooster.org/android/application/3270/


『<Project名>→app→main→java→com.example.widgetelsproperty→AppWidget(AppWidget.java)』(ファイルを作成)


package com.example.widgetelsproperty;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.IBinder;
import android.content.ComponentName;
import android.widget.RemoteViews;
import android.util.Log;

// WidgetELSProperty(Widget:{(Event Launch Service)(click)[Property]}).
public class AppWidget extends AppWidgetProvider {
    static final String fsDebugCrest = " 009";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    // Android Java の場合、カラーの実態は int。
    static final int[] d1iCPalette = new int[]{
            Color.BLUE,
            Color.CYAN,
            Color.GREEN,
            Color.MAGENTA,
            Color.RED,
            Color.YELLOW,
    };

// もし、ここで定義された Static 変数に Widget のメソッドでデータを設定したとしても、
// その(Widget の)メソッドが実行中は、当然 その Static 変数の値は保持されているだろうが、
// その後 その Static 変数の値が消失しているような感じなので、
// Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無い。
// 当然 Service 起動時に その Static 変数の値が残っている保障など無い。
// また、ここで定義された Static 変数に「Widget、Service」の
// どちらかが読み書きし そのどちらかが参照(又は読み書き)している場合、
// 競合する可能性があるので、そのような Static 変数を ここで定義してはならない。

    @Override
    public void onEnabled(Context ctx) {
        super.onEnabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onEnabled( );");
    }

    @Override
    public void onUpdate(Context ctx, AppWidgetManager awm, int[] awid) {
        super.onUpdate(ctx, awm, awid);
        if(DEBUG)Log.i(tag, "AppWidget.onUpdate( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");
        ComponentName cn = new ComponentName(ctx, AppWidget.class);
        RemoteViews rv = new RemoteViews(ctx.getPackageName( ), R.layout.widget_main);

        String vsMessage = "fsDebugCrest="+fsDebugCrest+";\n"+
                "onUpdate( );\n";
        rv.setTextViewText(R.id.ctvTextView, vsMessage);

        Intent si = new Intent(ctx, MyService.class);
        PendingIntent pi = PendingIntent.getService(ctx, 0, si, 0);
        rv.setOnClickPendingIntent(R.id.ctvTextView, pi);

        // awm.updateAppWidget により前述の2つの「rv.set〜」の設定が Update される。
        awm.updateAppWidget(cn, rv);
    }

    @Override
    public void onDeleted(Context ctx, int[] awid) {
        super.onDeleted(ctx, awid);
        if (DEBUG) Log.i(tag, "AppWidget.onDeleted( );");
    }

    @Override
    public void onDisabled(Context ctx){
        super.onDisabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onDisabled( );");
    }

    public static class MyService extends Service {

        @Override
        public void onCreate( ){
            super.onCreate( );
            if (DEBUG) Log.i(tag, "MyService.onCreate( );");
        }

        @Override
        public int onStartCommand(Intent si, int f, int sid) {
            super.onStartCommand(si, f, sid);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( );");
            // 前述の Widget の場合の記述とビミョウに違っている部分も有るので要注意。
            AppWidgetManager awm = AppWidgetManager.getInstance(this);
            ComponentName cn = new ComponentName(this, AppWidget.class);
            RemoteViews rv = new RemoteViews(getPackageName( ), R.layout.widget_main);

            // Android Java の場合、カラーの実態は int。
            int iColor = d1iCPalette[(int)(Math.random( ) * d1iCPalette.length)];
            rv.setInt(R.id.ctvTextView, "setBackgroundColor", iColor);
            String vsMessage = "MyService.onStartCommand( );\n"+
                    "iColor="+iColor+";\n";
            rv.setTextViewText(R.id.ctvTextView, vsMessage);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( ) : "+
                    "iColor="+iColor+"; "+
                    "");

            awm.updateAppWidget(cn, rv);

            return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
        }

        @Override
        public IBinder onBind(Intent si) {
            // super.onBind(si);
            return null;
        }

        @Override
        public void onDestroy( ){
            super.onDestroy( );
            if (DEBUG) Log.i(tag, "MyService.onDestroy( );");
        }

    }

}



『<Project名>→app→main→res→layout→widget_main.xml』(ファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ctvTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#c0c0c0"
    android:gravity="center"
    android:text="A Widget isn't launched."
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="#000000"
    android:editable="false" />



『<Project名>→app→main→res→xml→widget_info.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_main" />
    <!-- targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を 13以下にすれば良いらしい。 -->
    <!-- 「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。 -->



『<Project名>→app→main→AndroidManifest.xml』


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

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher" >
        <receiver
            android:name=".AppWidget" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
        <service android:name=".AppWidget$MyService">
        </service>
    </application>

</manifest>


<Number>: [000009E3]  <Date>: 2016/03/20 06:33:05
<Title>: WidgetILSToast 020
<Name>: amanojaku@管理人



どうも Widget から直接 Toast を使えないようで、Widget で Toast を表示したい場合は Service から Toast を表示させてやれば良いようだ。
また Widget のメソッド実行時に設定された(Widget Class 内の) Static 変数は、そのメソッドが実行中なら、当然 その値は保持されているだろうが、その後 その(Widget Class 内の) Static 変数の値が消失しているような感じなので、Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無いようだ、当然 Service 起動時に その(Widget Class 内の) Static 変数の値が残っている保障など無い。
よって Widget から Service へのデータの受け渡しには Wgeidt Class 内の Static 変数は使えない(Widget → Service の一方通行のデータの受け渡しに限定するなら、Intent により非常に簡単にデータを受け渡せる。
このプログラムで Intent によって Widget から Service にデータを受け渡すサンプルも記してみる。
TextView のデフォルト値に Text として「A Widget isn't launched.」を設定しているので、Widget を画面に設置した時に「A Widget isn't launched.」と表示されたら、Widget が起動していない事を意味する。
しばらく待てば Widget が起動するハズだが、端末が起動中の場合やデバッグ時などは Widget が起動するまで やたら時間がかかる場合がある。
つまり、実際の Widget アプリを作成する場合も、Widget が起動してない事が判るように何かメッセージを表示するとか、(メッセージを表示できない場合は) Widget が未起動時のアイコンと起動時のアイコンを切り替えて表示するとかの工夫が必要になる場合もあるでしょう。

とりあえず、[New Project]で(1ページ目)[Application name]:「WidgetILSToast」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。

targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を13以下にすれば良いらしい。
現状でインストールできる「API Level 13」以下の SDK Version の最大値は「API Level 10」(Android OS 2.3.3)となり、当方も「API Level 10」(Android OS 2.3.3)をインストールしている、よって targetSdkVersion の値は「10」としている。
なお、targetSdkVersion の設定方法は仕様が変更され「<プロジェクト名>→app→build.gradle」で設定しなければならないようだ(これらを「AndroidManifest.xml」で指定しても無視されるようだ)、「build.gradle」は同名のファイルが他にもあるようなので間違えないように注意が必要。
※必ず(Android Studio からではなく)オリジナルの「Android SDK Manager(SDK Manager.exe)」から「API Level 10」(Android OS 2.3.3)をインストールして下さい。
「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。


《参考》

Yukiの枝折: Android:Serviceの基本とonStartCommandの戻り値による ...
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html

Android Widgetのライフサイクルについて要点だけ | スマートフォン要点だけブログ
http://blog.imho.jp/2011/05/android-widget.html

Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ)
http://techbooster.org/android/application/3270/

WidgetELSProperty
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E1.+&


『<Project名>→app→main→java→com.example.widgetilstoast→AppWidget(AppWidget.java)』(ファイルを作成)


package com.example.widgetilstoast;

import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.content.ComponentName;
import android.view.Gravity;
import android.widget.RemoteViews;
import android.util.Log;
import android.widget.Toast;

// WidgetILSToast(Widget:{(Instant Launch Service)[Toast]}).
public class AppWidget extends AppWidgetProvider {
    static final String fsDebugCrest = " 020";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

// もし、ここで定義された Static 変数に Widget のメソッドでデータを設定したとしても、
// その(Widget の)メソッドが実行中は、当然 その Static 変数の値は保持されているだろうが、
// その後 その Static 変数の値が消失しているような感じなので、
// Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無い。
// 当然 Service 起動時に その Static 変数の値が残っている保障など無い。
// また、ここで定義された Static 変数に「Widget、Service」の
// どちらかが読み書きし そのどちらかが参照(又は読み書き)している場合、
// 競合する可能性があるので、そのような Static 変数を ここで定義してはならない。

    @Override
    public void onEnabled(Context ctx) {
        super.onEnabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onEnabled( );");
    }

    @Override
    public void onUpdate(Context ctx, AppWidgetManager awm, int[] awid) {
        super.onUpdate(ctx, awm, awid);
        if(DEBUG)Log.i(tag, "AppWidget.onUpdate( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");

        Intent si = new Intent(ctx, MyService.class);
        String vsMessage = "fsDebugCrest="+fsDebugCrest+";\n"+
                "AppWidget.onUpdate( );\n";
        si.putExtra("MessageKey", vsMessage);
        si.putExtra("ToastKey", "Hello, world!");
        ctx.startService(si);
    }

    @Override
    public void onDeleted(Context ctx, int[] awid) {
        super.onDeleted(ctx, awid);
        if (DEBUG) Log.i(tag, "AppWidget.onDeleted( );");
    }

    @Override
    public void onDisabled(Context ctx){
        super.onDisabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onDisabled( );");
    }

    public static class MyService extends Service {

        @Override
        public void onCreate( ){
            super.onCreate( );
            if (DEBUG) Log.i(tag, "MyService.onCreate( );");
        }

        @Override
        public int onStartCommand(Intent si, int f, int sid) {
            super.onStartCommand(si, f, sid);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( );");

            AppWidgetManager awm = AppWidgetManager.getInstance(this);
            ComponentName cn = new ComponentName(this, AppWidget.class);
            RemoteViews rv = new RemoteViews(getPackageName(), R.layout.widget_main);

            String vsMessage = si.getStringExtra("MessageKey");
            if( null==vsMessage ){
                vsMessage =  "fsDebugCrest="+fsDebugCrest+";\n";
            }
            vsMessage = vsMessage+
                    "MyService.onStartCommand( );\n";
            rv.setTextViewText(R.id.ctvTextView, vsMessage);

            awm.updateAppWidget(cn, rv);

            String vsToast = si.getStringExtra("ToastKey");
            if( null==vsToast ){
                vsToast =  "onStartCommand( )";
            }
            Toast.makeText(getApplicationContext( ), vsToast, Toast.LENGTH_SHORT).show();

            return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
        }

        @Override
        public IBinder onBind(Intent si) {
            // super.onBind(si);
            return null;
        }

        @Override
        public void onDestroy( ){
            super.onDestroy( );
            if (DEBUG) Log.i(tag, "MyService.onDestroy( );");
        }

    }

}



『<Project名>→app→main→res→layout→widget_main.xml』(ファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ctvTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333333"
    android:gravity="center"
    android:text="A Widget isn't launched."
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="#ffffff" />



『<Project名>→app→main→res→xml→widget_info.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_main" />
    <!-- targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を 13以下にすれば良いらしい。 -->
    <!-- 「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。 -->



『<Project名>→app→main→AndroidManifest.xml』


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

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher" >
        <receiver
            android:name=".AppWidget" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
        <service android:name=".AppWidget$MyService">
        </service>
    </application>

</manifest>


<Number>: [000009F0]  <Date>: 2016/03/20 06:35:13
<Title>: WidgetELActivity 003
<Name>: amanojaku@管理人



Widget から Activity を起動。
以前 作ったモノを元に AppCompat を起動してみる。
TextView のデフォルト値に Text として「A Widget isn't launched.」を設定しているので、Widget を画面に設置した時に「A Widget isn't launched.」と表示されたら、Widget が起動していない事を意味する。
しばらく待てば Widget が起動するハズだが、端末が起動中の場合やデバッグ時などは Widget が起動するまで やたら時間がかかる場合がある。
つまり、実際の Widget アプリを作成する場合も、Widget が起動してない事が判るように何かメッセージを表示するとか、(メッセージを表示できない場合は) Widget が未起動時のアイコンと起動時のアイコンを切り替えて表示するとかの工夫が必要になる場合もあるでしょう。

とりあえず、[New Project]で(1ページ目)[Application name]:「WidgetELActivity」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
「drawable-mdpi、drawable-hdpi」フォルダー(存在しない場合は作成してやる)内に「Android SDK」のデフォルト・アイコン「ic_menu_info_details.png、ic_menu_moreoverflow_normal_holo_light.png」をコピーする。
「Toolbar」の UI はスタンダードな「ActionBarActivity」のような感じになっている。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。

「drawable-mdpi、drawable-hdpi」フォルダー(存在しない場合は作成してやる)内に「Android SDK」のデフォルト・アイコン「ic_menu_moreoverflow_normal_holo_light.png」をコピーする。
一応、アプリの Logo マークを Android のマスコット・キャラの「Droid君」にしている。
下の方に"drawable-hdpi"フォルダー用の画像ファイル(「48x48」Pixel)をアップしてあります。
"drawable-mdpi"フォルダー用は手抜きして作ってないが、解像度が低い端末でも自動的に その端末の解像度に合うように縮小して表示される。
その場合、当然 画質は悪くなるので画質を綺麗にしたい場合は手抜きせずにチャント"drawable-mdpi"フォルダー用の画像ファイル(「32x32」Pixel)を作成してやれば良い。
「Toolbar」の UI はスタンダードな「ActionBarActivity」のような感じになっている。

targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を13以下にすれば良いらしい。
現状でインストールできる「API Level 13」以下の SDK Version の最大値は「API Level 10」(Android OS 2.3.3)となり、当方も「API Level 10」(Android OS 2.3.3)をインストールしている、よって targetSdkVersion の値は「10」としている。
なお、targetSdkVersion の設定方法は仕様が変更され「<プロジェクト名>→app→build.gradle」で設定しなければならないようだ(これらを「AndroidManifest.xml」で指定しても無視されるようだ)、「build.gradle」は同名のファイルが他にもあるようなので間違えないように注意が必要。
※必ず(Android Studio からではなく)オリジナルの「Android SDK Manager(SDK Manager.exe)」から「API Level 10」(Android OS 2.3.3)をインストールして下さい。
「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。


《参考》

Yukiの枝折: Android:Serviceの基本とonStartCommandの戻り値による ...
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html

Android Widgetのライフサイクルについて要点だけ | スマートフォン要点だけブログ
http://blog.imho.jp/2011/05/android-widget.html

WidgetELSProperty
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E1.+&
WidgetILSToast
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E3.+&


『<Project名>→app→main→java→com.example.widgeteactivity→AppWidget(AppWidget.java)』(ファイルを作成)


package com.example.widgetelactivity;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.content.ComponentName;
import android.widget.RemoteViews;
import android.util.Log;

// WidgetELActivity(Widget:{(Event Launch Activity)(click)}).
public class AppWidget extends AppWidgetProvider {
    static final String fsDebugCrest = " 003";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

// もし、ここで定義された Static 変数に Widget のメソッドでデータを設定したとしても、
// その(Widget の)メソッドが実行中は、当然 その Static 変数の値は保持されているだろうが、
// その後 その Static 変数の値が消失しているような感じなので、
// Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無い。
// 当然 Service 起動時に その Static 変数の値が残っている保障など無い。
// また、ここで定義された Static 変数に「Widget、Service」の
// どちらかが読み書きし そのどちらかが参照(又は読み書き)している場合、
// 競合する可能性があるので、そのような Static 変数を ここで定義してはならない。

    @Override
    public void onEnabled(Context ctx) {
        super.onEnabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onEnabled( );");
    }

    @Override
    public void onUpdate(Context ctx, AppWidgetManager awm, int[] awid) {
        super.onUpdate(ctx, awm, awid);
        if(DEBUG)Log.i(tag, "AppWidget.onUpdate( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");
        ComponentName cn = new ComponentName(ctx, AppWidget.class);
        RemoteViews rv = new RemoteViews(ctx.getPackageName(), R.layout.widget_main);

        String vsMessage = "fsDebugCrest="+fsDebugCrest+";\n"+
                "onUpdate( );\n";
        rv.setTextViewText(R.id.ctvTextView, vsMessage);

        // 起動するアクティビティの指定
        Intent si = new Intent(ctx, MainActivity.class);
        PendingIntent pi = PendingIntent.getActivity(ctx, 0, si, 0);
        rv.setOnClickPendingIntent(R.id.ctvTextView, pi);

        // awm.updateAppWidget により前述の2つの「rv.set〜」の設定が Update される。
        awm.updateAppWidget(cn, rv);
    }

    @Override
    public void onDeleted(Context ctx, int[] awid) {
        super.onDeleted(ctx, awid);
        if (DEBUG) Log.i(tag, "AppWidget.onDeleted( );");
    }

    @Override
    public void onDisabled(Context ctx){
        super.onDisabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onDisabled( );");
    }

    public static class MyService extends Service {

        @Override
        public void onCreate( ){
            super.onCreate( );
            if (DEBUG) Log.i(tag, "MyService.onCreate( );");
        }

        @Override
        public int onStartCommand(Intent intent, int f, int sid) {
            super.onStartCommand(intent, f, sid);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( );");
            // 前述の Widget の場合の記述とビミョウに違っている部分も有るので要注意。
            AppWidgetManager awm = AppWidgetManager.getInstance(this);
            ComponentName cn = new ComponentName(this, AppWidget.class);
            RemoteViews rv = new RemoteViews(getPackageName(), R.layout.widget_main);

            String vsMessage = "MyService.onStartCommand( );\n";
            rv.setTextViewText(R.id.ctvTextView, vsMessage);

            awm.updateAppWidget(cn, rv);

            return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
        }

        @Override
        public IBinder onBind(Intent intent) {
            // super.onBind(intent);
            return null;
        }

        @Override
        public void onDestroy( ){
            super.onDestroy( );
            if (DEBUG) Log.i(tag, "MyService.onDestroy( );");
        }

    }

}



『<Project名>→app→main→res→layout→widget_main.xml』(ファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ctvTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333333"
    android:gravity="center"
    android:text="A Widget isn't launched."
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="#ffffff" />



『<Project名>→app→main→res→xml→widget_info.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_main" />
    <!-- targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を 13以下にすれば良いらしい。 -->
    <!-- 「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。 -->



『<Project名>→app→main→java→com.example.widgetelactivity→MainActivity(MainActivity.java)』


package com.example.widgetelactivity;

import android.os.Bundle;
import android.util.Log;
import android.app.Activity;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
// import android.widget.Toolbar;
import android.content.Context;

import android.view.View;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import android.widget.TextView;
import android.view.KeyEvent;

import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;

public class MainActivity extends AppCompatActivity
        implements View.OnClickListener {
    static final String fsDebugCrest = " 008";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。
    MainActivity oMainApp;
    Context oBaseContext;
    Context oAppContext;
    Context oContext;
    ActionBar oActionBar;
    Menu oMainMenu;

    String vsPackageName;
    String vsVersionName;
    int iVersionCode;
    String vsToolbarTitle;
    static final int[] WIDGETS =
            new int[]{R.id.button1, R.id.checkBox1, R.id.toggleButton1, R.id.radioButton1};
    TextView ctvMessage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(DEBUG)Log.i(tag, "Activity.onCreate( );");
        setContentView(R.layout.activity_main);
        oMainApp = this;
// System 共通の Context:別の Application とやりとりするとき。
        oBaseContext = getBaseContext( );
// Application 固有の Context(Application ごとに Context が変化する):Application 共通のモノが対象。
        oAppContext = getApplicationContext( );
// Activity 固有の Context(Activity ごとに Context が変化する):Activity に依存モノが対象。
        oContext = this;

        vsPackageName = getPackageName( );
        try {
            PackageInfo packageInfo = this.getPackageManager( ).getPackageInfo(
                    this.getPackageName( ), PackageManager.GET_META_DATA );
            // 「<Project名>→app→build.gradle」ファイル内の
            // 「versionName、versionCode」の設定値を取得。
            vsVersionName = packageInfo.versionName;
            iVersionCode = packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            // 例外処理
            e.printStackTrace( );
        }

        ctvMessage = (TextView)findViewById(R.id.message);
        ctvMessage.append(
                "PackageName=" + vsPackageName + ";\n" +
                        "VersionName=" + vsVersionName + ";\n" +
                        "BuildNo=" + String.valueOf(iVersionCode) + ";\n" +
                        "");

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        oActionBar = getSupportActionBar();

        // oActionBar.setIcon(R.drawable.ic_menu_info_details);
        oActionBar.setLogo(R.drawable.x_logo_android022);
        // ↑ 実際は Logo マークはアプリ固有の Icon にすべきモノです。
        // アプリ固有の Logo マークと言われてもイメージが湧かない方は
        // ブラウザの Icon(Logo マーク)をイメージしてみて下さい。
        // 下記サイトに主要ブラウザの Icon(Logo マーク)が掲載されています。
        // http://freesoft-100.com/pasokon/browser.html
        // ちなみに Toolbar に表示する Icon サイズのスタンダードは
        // "drawable-mdpi"フォルダー用は「32x32」、
        // "drawable-hdpi"フォルダー用は「48x48」だと思われます。

        vsToolbarTitle = "  "+getString(R.string.app_name)+
                fsDebugCrest+
                " V"+vsVersionName+ // Version.
                " R"+String.valueOf(iVersionCode); // Revision.
// ↑ Toolbar に Logo マークを表示させるとタイトルと Logo マークとの間に隙間がないので、
// タイトルの先頭に半角スペースを2つ入れている。
        oActionBar.setTitle(vsToolbarTitle);

        for (int iWgId : WIDGETS) {
            View cvw = findViewById(iWgId);
            cvw.setOnClickListener(this);
        }
    }

    @Override
    protected void onRestart( ) {
        super.onRestart();
        if(DEBUG)Log.i(tag, "Activity.onRestart( );");
    }

    @Override
    protected void onStart( ) {
        super.onStart();
        if(DEBUG)Log.i(tag, "Activity.onStart( );");
    }

    @Override
    protected void onResume( ) {
        super.onResume();
        if(DEBUG)Log.i(tag, "Activity.onResume( );");
    }

    @Override
    protected void onPause( ) {
        super.onPause();
        if(DEBUG)Log.i(tag, "Activity.onPause( );");
    }

    @Override
    protected void onStop( ) {
        super.onStop();
        if(DEBUG)Log.i(tag, "Activity.onStop( );");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(DEBUG)Log.i(tag, "Activity.onDestroy( );");
    }

    @Override
    public void onClick(View v) {
        // super.onClick(v);
        if(DEBUG) Log.i(tag,"Activity.onClick( );");
        switch(v.getId()) {
            case R.id.button1 :
                if (DEBUG) Log.i(tag, "button1");

                break;
            case R.id.checkBox1 :
                if (DEBUG) Log.i(tag, "checkBox1");

                break;
            case R.id.toggleButton1 :
                if (DEBUG) Log.i(tag, "toggleButton1");

                break;
            case R.id.radioButton1 :
                if (DEBUG) Log.i(tag, "radioButton1");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
                return;
        }
        return;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        // アクションバー内で使用する為のメニューアイテムを実体化
        MenuInflater inflater = getMenuInflater();
        // MenuInflater inflater = getSupportMenuInflater();
        inflater.inflate(R.menu.main, menu);
        // 「R.menu.main」の「menu.main」は 恐らく「res」からの「フォルダー名+ファイル名」
        oMainMenu = menu;
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        if(DEBUG) Log.i(tag,"Activity.onOptionsItemSelected( );");
        switch(item.getItemId()) {
            case R.id.menu_main_settings :
                if (DEBUG) Log.i(tag, "menu_main_settings");

                break;
            case R.id.menu_main_settings_item1 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item1");

                break;
            case R.id.menu_main_settings_item2 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item2");

                break;
            case R.id.menu_main_settings_item3 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item3");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                return false; // true; //
        }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        super.dispatchKeyEvent(event);
        if(DEBUG) Log.i(tag,"Activity.dispatchKeyEvent( );");
        final int action = event.getAction();
        final int keyCode = event.getKeyCode();
        switch(keyCode) {
            case KeyEvent.KEYCODE_MENU :
                if(DEBUG) Log.i(tag,"KeyEvent.KEYCODE_MENU;");
                if ( action==KeyEvent.ACTION_UP ) {
                    if(DEBUG) Log.i(tag,"KeyEvent.ACTION_UP;");
                    if ( oMainMenu!=null ) {
                        if(DEBUG) Log.i(tag,"oMainMenu.performIdentifierAction( );");
                        oMainMenu.performIdentifierAction(R.id.menu_main_settings, 0);
                    }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
                    return true; // false; //
                }
                break;

            default :
                if (DEBUG) Log.i(tag, "KeyEvent:Default");

                break;
        }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
        return false; // true; //
    }

}



『<Project名>→app→main→res→drawable→border.xml』(ファイルを作成)
※「TextView」の枠として使っている。


<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ffffff" />
    <padding android:top="3dp" android:bottom="3dp"
        android:left="7dp" android:right="7dp" />
    <stroke android:width="1px" android:color="#000000" />
    <corners android:radius="5dp" />
</shape>



『<Project名>→app→main→res→layout→activity_main.xml』


<?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"
    tools:context=".MainActivity"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/actionBarSize"
        android:background="#9b7fff"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <TextView
        android:id="@+id/message"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_margin = "7dp"
        android:background="@drawable/border"
        android:textSize="@dimen/abc_text_size_medium_material"
        android:layout_weight="1" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
        android:id="@+id/button1"
        android:layout_gravity="center_horizontal" />

    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ToggleButton 1"
        android:id="@+id/toggleButton1"
        android:layout_gravity="center_horizontal" />

    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox 1"
        android:id="@+id/checkBox1"
        android:layout_gravity="center_horizontal" />

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="RadioButton 1"
        android:id="@+id/radioButton1"
        android:layout_gravity="center_horizontal" />

</LinearLayout>



『<Project名>→app→src→main→res→menu→main.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<menu
    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"
    tools:context=".MainActivity">

    <item android:id="@+id/menu_main_settings"
        android:title="Settings"
        android:icon="@drawable/ic_menu_moreoverflow_normal_holo_light"
        app:showAsAction="always">
        <!-- ↑ showAsAction:Toolbar に対する表示設定。 -->
        <!-- "always":常に表示、"never":常に表示しない、"ifRoom":表示する余裕があれば表示。 -->
        <menu>
            <item android:id="@+id/menu_main_settings_item1"
                android:title="item1">
            </item>

            <item android:id="@+id/menu_main_settings_item2"
                android:title="item2">
            </item>

            <item android:id="@+id/menu_main_settings_item3"
                android:title="item3">
            </item>
        </menu>
    </item>

</menu>



『<Project名>→app→src→main→res→values→styles.xml』


<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>



『<Project名>→app→main→AndroidManifest.xml』
※この場合 <activity>タグに"launchMode"は設定できないようだ、この場合 「launchMode="singleInstance"」を設定しなくても Activity のインスタンスは1つだけに限定されるようだ。


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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
            android:name=".AppWidget"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
        <activity android:name=".MainActivity" >
        </activity>
    </application>

</manifest>


<Number>: [000009FA]  <Date>: 2016/04/30 15:22:37
<Title>: WidgetELSStorage 012
<Name>: amanojaku@管理人



「Widget、Service」から Storage に読み書きする。
今回は「内部ストレージのアプリ固有の領域」に対して Stream を読み書きしてみる。
Widget の onEnabled 実行時に Stream のデータを消去。
Widget の onUpdate 実行時に、onUpdate イベントのログを Stream に追加する。
クリックによる Service の onStartCommand 実行時に、onStartCommand イベントのログを Stream に追加する。
なお、「内部ストレージのアプリ固有の領域」は(セキュリティー上)ファイラーからは見えないようになってるらしい。
ちなみに「外部ストレージ領域」はファイラーから見えるので、「パスワード、個人情報」などのようなデータは「外部ストレージ領域」に保存してはならない。
TextView のデフォルト値に Text として「A Widget isn't launched.」を設定しているので、Widget を画面に設置した時に「A Widget isn't launched.」と表示されたら、Widget が起動していない事を意味する。
しばらく待てば Widget が起動するハズだが、端末が起動中の場合やデバッグ時などは Widget が起動するまで やたら時間がかかる場合がある。
つまり、実際の Widget アプリを作成する場合も、Widget が起動してない事が判るように何かメッセージを表示するとか、(メッセージを表示できない場合は) Widget が未起動時のアイコンと起動時のアイコンを切り替えて表示するとかの工夫が必要になる場合もあるでしょう。

とりあえず、[New Project]で(1ページ目)[Application name]:「WidgetELSStorage」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。

targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を13以下にすれば良いらしい。
現状でインストールできる「API Level 13」以下の SDK Version の最大値は「API Level 10」(Android OS 2.3.3)となり、当方も「API Level 10」(Android OS 2.3.3)をインストールしている、よって targetSdkVersion の値は「10」としている。
なお、targetSdkVersion の設定方法は仕様が変更され「<プロジェクト名>→app→build.gradle」で設定しなければならないようだ(これらを「AndroidManifest.xml」で指定しても無視されるようだ)、「build.gradle」は同名のファイルが他にもあるようなので間違えないように注意が必要。
※必ず(Android Studio からではなく)オリジナルの「Android SDK Manager(SDK Manager.exe)」から「API Level 10」(Android OS 2.3.3)をインストールして下さい。
「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。


《参考》

Yukiの枝折: Android:Serviceの基本とonStartCommandの戻り値による ...
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html

Android Widgetのライフサイクルについて要点だけ | スマートフォン要点だけブログ
http://blog.imho.jp/2011/05/android-widget.html

Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ)
http://techbooster.org/android/application/3270/

アプリからデータを内部&外部ストレージへの保存
http://androidadvent.blog.shinobi.jp/2012/internal-external-storage-save

AndroidのContextについて
http://qiita.com/tyfkda/items/bbb77a0e5a97eda9d4b6

> getApplicationContext()
> getBaseContext()

> どれを使うべきか、はたまたgetApplicationで取得できるApplicationもContextを継承していて、どれをつかったらいいの?と悩む。

> Activityはアクティビティ単位なので、別のアクティビティに遷移するとContextも変わる
> Applicationは起動中のアプリに対して1個存在して、Contextも1つ。アプリが生きている間存在するアクティビティが変わっても同じ内容
> getBaseContextは別のアプリとやりとりするとき用

> OSからみてアプリケーションの機能として見ることができるモノ(SharedPreferencesやDB,リソースへのアクセスなど)はApplicationContextを用い,Activityに依存するモノ(BroadcastReceiverやViewのインスタンス生成など)はActivityを用いるもの

WidgetELSProperty
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E1.+&
WidgetILSToast
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E3.+&
WidgetELActivity
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009F0.+&


『<Project名>→app→main→java→com.example.widgetelsstorage→AppWidget(AppWidget.java)』(ファイルを作成)


package com.example.widgetelsstorage;

import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.IBinder;
import android.content.ComponentName;
import android.widget.RemoteViews;
import android.util.Log;

import java.io.File;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.io.IOException;

import java.util.List;
import java.util.ArrayList;

import java.text.SimpleDateFormat;
import java.util.Date;

// WidgetELSStorage(Widget:{(Event Launch Service)(click)[Storage]}).
public class AppWidget extends AppWidgetProvider {
    static final String fsDebugCrest = " 012";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    static final String fvsSerialFile = "Storage.dat";
    static final SimpleDateFormat oDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");

    // Android Java の場合、カラーの実態は int。
    static final int[] d1iCPalette = new int[]{
            Color.BLUE,
            Color.CYAN,
            Color.GREEN,
            Color.MAGENTA,
            Color.RED,
            Color.YELLOW,
    };

// もし、ここで定義された Static 変数に Widget のメソッドでデータを設定したとしても、
// その(Widget の)メソッドが実行中は、当然 その Static 変数の値は保持されているだろうが、
// その後 その Static 変数の値が消失しているような感じなので、
// Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無い。
// 当然 Service 起動時に その Static 変数の値が残っている保障など無い。
// また、ここで定義された Static 変数に「Widget、Service」の
// どちらかが読み書きし そのどちらかが参照(又は読み書き)している場合、
// 競合する可能性があるので、そのような Static 変数を ここで定義してはならない。

    @Override
    public void onEnabled(Context ctx) {
        super.onEnabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onEnabled( );");

        Context oAppContext = ctx.getApplicationContext( );
        // 初期化。
        List<String> dl1Event = new ArrayList<String>( );
        Write(oAppContext, dl1Event);
    }

    @Override
    public void onUpdate(Context ctx, AppWidgetManager awm, int[] awid) {
        super.onUpdate(ctx, awm, awid);
        if(DEBUG)Log.i(tag, "AppWidget.onUpdate( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");
        Context oAppContext = ctx.getApplicationContext( );
        List<String> dl1Event = new ArrayList<String>( );
        Read(oAppContext, dl1Event);
        dl1Event.add("AppWidget.onUpdate");
        Write(oAppContext, dl1Event);

        ComponentName cn = new ComponentName(ctx, AppWidget.class);
        RemoteViews rv = new RemoteViews(ctx.getPackageName( ), R.layout.widget_main);

        String vsMessage = "fsDebugCrest="+fsDebugCrest+";\n"+
                "onUpdate( );\n";
        rv.setTextViewText(R.id.ctvTextView, vsMessage);

        Intent si = new Intent(ctx, MyService.class);
        PendingIntent pi = PendingIntent.getService(ctx, 0, si, 0);
        rv.setOnClickPendingIntent(R.id.ctvTextView, pi);

        // awm.updateAppWidget により前述の2つの「rv.set〜」の設定が Update される。
        awm.updateAppWidget(cn, rv);
    }

    @Override
    public void onDeleted(Context ctx, int[] awid) {
        super.onDeleted(ctx, awid);
        if (DEBUG) Log.i(tag, "AppWidget.onDeleted( );");
    }

    @Override
    public void onDisabled(Context ctx){
        super.onDisabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onDisabled( );");
    }

    static void Read(Context oAppContext, List<String> dl1Event) {
        if(DEBUG)Log.i(tag, "AppWidget.Read( );");
        try {
            File vsFFDir = oAppContext.getFilesDir();
            if(DEBUG)Log.i(tag,"AppWidget.Read( ) : "+
                    "vsFFDir.getCanonicalPath()="+vsFFDir.getCanonicalPath()+"; "+
                    "");
            File vsFSPath = oAppContext.getFileStreamPath(fvsSerialFile);
            if(DEBUG)Log.i(tag,"AppWidget.Read( ) : "+
                    "vsFSPath.getCanonicalPath()="+vsFSPath.getCanonicalPath()+"; "+
                    "");
            FileInputStream oFIS = oAppContext.openFileInput(fvsSerialFile);
            BufferedInputStream oBIS = new BufferedInputStream(oFIS);
            ObjectInputStream oOISSerial = new ObjectInputStream(oBIS);
            int iListSize = ((Integer)oOISSerial.readObject( )).intValue( );
            if(DEBUG)Log.i(tag,"AppWidget.Read( ) : "+
                    "iListSize="+iListSize+"; "+
                    "");
            for( int i= 0; i<iListSize; i++ ){
                dl1Event.add((String)oOISSerial.readObject( ));
                if(DEBUG)Log.i(tag,"AppWidget.Read( ) : "+
                        "dl1Event.get(i)="+dl1Event.get(i)+"; "+
                        "");
            }
            oOISSerial.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            // e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    static void Write(Context oAppContext, List<String> dl1Event) {
        if(DEBUG)Log.i(tag, "AppWidget.Write( );");
        try {
            if(DEBUG)Log.i(tag,"AppWidget.Write( ) : "+
                    "dl1Event.size( )="+dl1Event.size( )+"; "+
                    "");
            File vsFFDir = oAppContext.getFilesDir();
            if(DEBUG)Log.i(tag,"AppWidget.Write( ) : "+
                    "vsFFDir.getCanonicalPath()="+vsFFDir.getCanonicalPath()+"; "+
                    "");
            // Context の openFileOutput は内部ストレージのアプリ固有の領域にアクセスされるらしい。
            // Context.MODE_PRIVATE:PRIVATE 出力モード:他のアプリからはアクセスできない。
            FileOutputStream oFOS = oAppContext.openFileOutput(fvsSerialFile, Context.MODE_PRIVATE);
            BufferedOutputStream oBOS = new BufferedOutputStream(oFOS);
            ObjectOutputStream oOOSSerial = new ObjectOutputStream(oBOS);
            oOOSSerial.writeObject(new Integer(dl1Event.size()));
            for( int i = 0; i<dl1Event.size( ); i++ ){
                oOOSSerial.writeObject(dl1Event.get(i));
            }
            oOOSSerial.close();
            File vsFSPath = oAppContext.getFileStreamPath(fvsSerialFile);
            if(DEBUG)Log.i(tag,"AppWidget.Write( ) : "+
                    "vsFSPath.getCanonicalPath()="+vsFSPath.getCanonicalPath()+"; "+
                    "");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static class MyService extends Service {

        @Override
        public void onCreate( ){
            super.onCreate( );
            if (DEBUG) Log.i(tag, "MyService.onCreate( );");
        }

        @Override
        public int onStartCommand(Intent si, int f, int sid) {
            super.onStartCommand(si, f, sid);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( );");

            String vsDateTime = oDateFormat.format(
                    new Date(System.currentTimeMillis()));
            if (DEBUG) Log.i(tag, "AppWidget : " +
                    "vsDateTime=" +vsDateTime+ "; " +
                    "");
            Context oAppContext = getApplicationContext();
            List<String> dl1Event = new ArrayList<String>( );
            Read(oAppContext, dl1Event);
            dl1Event.add("MyService.onStartCommand : "+
                    "vsDateTime=" +vsDateTime);
            Write(oAppContext, dl1Event);

            // 前述の Widget の場合の記述とビミョウに違っている部分も有るので要注意。
            AppWidgetManager awm = AppWidgetManager.getInstance(this);
            ComponentName cn = new ComponentName(this, AppWidget.class);
            RemoteViews rv = new RemoteViews(getPackageName( ), R.layout.widget_main);

            // Android Java の場合、カラーの実態は int。
            int iColor = d1iCPalette[(int)(Math.random( ) * d1iCPalette.length)];
            rv.setInt(R.id.ctvTextView, "setBackgroundColor", iColor);
            String vsMessage = "MyService.onStartCommand( );\n"+
                    "iColor="+iColor+";\n";
            rv.setTextViewText(R.id.ctvTextView, vsMessage);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( ) : "+
                    "iColor="+iColor+"; "+
                    "");

            awm.updateAppWidget(cn, rv);

            return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
        }

        @Override
        public IBinder onBind(Intent si) {
            // super.onBind(si);
            return null;
        }

        @Override
        public void onDestroy( ){
            super.onDestroy( );
            if (DEBUG) Log.i(tag, "MyService.onDestroy( );");
        }

    }

}



『<Project名>→app→main→res→layout→widget_main.xml』(ファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ctvTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#c0c0c0"
    android:gravity="center"
    android:text="A Widget isn't launched."
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="#000000"
    android:editable="false" />



『<Project名>→app→main→res→xml→widget_info.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_main" />
    <!-- targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を 13以下にすれば良いらしい。 -->
    <!-- 「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。 -->



『<Project名>→app→main→AndroidManifest.xml』


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

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher" >
        <receiver
            android:name=".AppWidget" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
        <service android:name=".AppWidget$MyService">
        </service>
    </application>

</manifest>


<Number>: [00000A05]  <Date>: 2016/04/16 00:43:37
<Title>: WidgetILSTimerILSBKill 006
<Name>: amanojaku@管理人



Widget で(Service から) Timer を起動、Broadcast による通知で Timer を停止する。
秒単位での短い間隔の繰り返しには Timer オブジェクトが適してるらしいので、Timer オブジェクトを使用する。
Widget Class 内のフィールド変数ではデータが保持されないので、Timer オブジェクトを Service Class 内のフィールド変数として定義し、Service から起動する。
Broadcast で Timer に通知して停止させる、良く分からないが Widget だと「getBaseContext( )」が使えないようで、それだと Broadcast できないと言うことになるから、結局 (Broadcast に関しては) Service から実行する必要があるようだ。
問題点としては このプログラムでは Timer を1つしか作れないので、複数の Timer が必要な場合は何らかの工夫が必要となる。
TextView のデフォルト値に Text として「A Widget isn't launched.」を設定しているので、Widget を画面に設置した時に「A Widget isn't launched.」と表示されたら、Widget が起動していない事を意味する。
しばらく待てば Widget が起動するハズだが、端末が起動中の場合やデバッグ時などは Widget が起動するまで やたら時間がかかる場合がある。
つまり、実際の Widget アプリを作成する場合も、Widget が起動してない事が判るように何かメッセージを表示するとか、(メッセージを表示できない場合は) Widget が未起動時のアイコンと起動時のアイコンを切り替えて表示するとかの工夫が必要になる場合もあるでしょう。

とりあえず、[New Project]で(1ページ目)[Application name]:「WidgetILSTimerILSBKill」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。

targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を13以下にすれば良いらしい。
現状でインストールできる「API Level 13」以下の SDK Version の最大値は「API Level 10」(Android OS 2.3.3)となり、当方も「API Level 10」(Android OS 2.3.3)をインストールしている、よって targetSdkVersion の値は「10」としている。
なお、targetSdkVersion の設定方法は仕様が変更され「<プロジェクト名>→app→build.gradle」で設定しなければならないようだ(これらを「AndroidManifest.xml」で指定しても無視されるようだ)、「build.gradle」は同名のファイルが他にもあるようなので間違えないように注意が必要。
※必ず(Android Studio からではなく)オリジナルの「Android SDK Manager(SDK Manager.exe)」から「API Level 10」(Android OS 2.3.3)をインストールして下さい。
「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。


《参考》

Yukiの枝折: Android:Serviceの基本とonStartCommandの戻り値による ...
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html

Android Widgetのライフサイクルについて要点だけ | スマートフォン要点だけブログ
http://blog.imho.jp/2011/05/android-widget.html

Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ)
http://techbooster.org/android/application/3270/

WidgetELSProperty
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E1.+&
WidgetILSToast
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E3.+&
WidgetELActivity
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009F0.+&
WidgetELSStorage
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009FA.+&


『<Project名>→app→main→java→com.example.widgetilstimerilsbkill→AppWidget(AppWidget.java)』(ファイルを作成)


package com.example.widgetilstimerilsbkill;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.content.ComponentName;
import android.widget.RemoteViews;
import android.util.Log;

import java.util.Timer;

// WidgetILSTimerILSBKill(Widget:{(Instant Launch Service)[Timer],(Instant Launch Service)[Broadcast Kill]}).
public class AppWidget extends AppWidgetProvider {
    static final String fsDebugCrest = " 006";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

// (世界中で一意の「Intent Filter 名」とするため)「Intent Filter」に設定する文字列は
// パッケージ名「自分のサイトのドメイン名を逆転させた名前+アプリケーション名」を設定する事が推奨されている。
// ここではパッケージ名 自体が手抜きなので とりあえず
// 「国名+自分の大まかな出生地+ハンドル・ネーム+アプリケーション名」としている
// (出生地にしておけば引っ越しても変更する必要がない)。
// これは あくまで暫定的な一例であり、実際の「Intent Filter 名」は もっとチャントしたモノが良いだろう。
// もし、ここの「Intent Filter」を変更した場合は
// 「AndroidManifest.xml」の「Intent Filter 名」も変更しなければならない。
// 本来、「パッケージ名」自体が もっとチャントしたモノになっていれば、
// 「getPackageName( )」を使えば良いだけなだが…。
// このプログラムでは、この「fsIntentFilter_Action」変数に「null」を設定すれば、
// 「getPackageName( )」の値が使用されるようになっている。
// その場合も 当然「AndroidManifest.xml」の「Intent Filter 名」を変更しなければならない。
// ただイキナリ、世界中で一意の「Intent Filter 名」を考え出すのも難しいかもしれないので、
// とりあえず このような変数を使って試行錯誤すると言う手もある
// (試行錯誤する場合、いちいち「パッケージ名」自体を変更するのは面倒)。
    static final String fsIntentFilter_Action = "jp.kanagawa.amanojaku.WidgetILSTimerILSBKill";

// もし、ここで定義された Static 変数に Widget のメソッドでデータを設定したとしても、
// その(Widget の)メソッドが実行中は、当然 その Static 変数の値は保持されているだろうが、
// その後 その Static 変数の値が消失しているような感じなので、
// Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無い。
// 当然 Service 起動時に その Static 変数の値が残っている保障など無い。
// また、ここで定義された Static 変数に「Widget、Service」の
// どちらかが読み書きし そのどちらかが参照(又は読み書き)している場合、
// 競合する可能性があるので、そのような Static 変数を ここで定義してはならない。

    @Override
    public void onEnabled(Context ctx) {
        super.onEnabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onEnabled( );");

// サービスの起動を1回だけに限定したい場合は、サービスの起動を onEnabled メソッド内に記述する。
        Intent si = new Intent(ctx, MyService.class);
        ctx.startService(si);
    }

    @Override
    public void onUpdate(Context ctx, AppWidgetManager awm, int[] awid) {
        super.onUpdate(ctx, awm, awid);
        if(DEBUG)Log.i(tag, "AppWidget.onUpdate( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");
        ComponentName cn = new ComponentName(ctx, AppWidget.class);
        RemoteViews rv = new RemoteViews(ctx.getPackageName(), R.layout.widget_main);

        String vsMessage = "fsDebugCrest="+fsDebugCrest+";\n"+
                "onUpdate( );\n";
        rv.setTextViewText(R.id.ctvTextView, vsMessage);

        awm.updateAppWidget(cn, rv);
    }

    @Override
    public void onDeleted(Context ctx, int[] awid) {
        super.onDeleted(ctx, awid);
        if (DEBUG) Log.i(tag, "AppWidget.onDeleted( );");
    }

    @Override
    public void onDisabled(Context ctx){
        super.onDisabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onDisabled( );");

        Intent si = new Intent(ctx, MyService.class);
        si.putExtra("KillKey", Boolean.valueOf(true)); // false; //
        ctx.startService(si);
        /*
        Intent si = new Intent(ctx, MyService.class);
        ctx.stopService(si);
        */
    }

    public static class MyService extends Service {

        Timer timer; // = new Timer( );

        @Override
        public void onCreate( ){
            super.onCreate( );
            if (DEBUG) Log.i(tag, "MyService.onCreate( );");
        }

        @Override
        public int onStartCommand(Intent si, int f, int sid) {
            super.onStartCommand(si, f, sid);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( );");

            AppWidgetManager awm = AppWidgetManager.getInstance(this);
            ComponentName cn = new ComponentName(this, AppWidget.class);
            RemoteViews rv = new RemoteViews(getPackageName(), R.layout.widget_main);

            String vsMessage = "MyService.onStartCommand( );\n";
            rv.setTextViewText(R.id.ctvTextView, vsMessage);

            awm.updateAppWidget(cn, rv);

            Boolean oKill = si.getBooleanExtra("KillKey", false);
            if( ! oKill.booleanValue( ) ){
                if(DEBUG)Log.i(tag, "MyTimerTask( );");
                long delay = 0; // 0ms
                long interval = 1000; // 1000ms
                timer = new Timer( );
                timer.scheduleAtFixedRate(new MyTimerTask(timer), delay, interval);
            }else{
                if(DEBUG)Log.i(tag, "Kill;");
                String ifa = fsIntentFilter_Action;
                if( null==ifa ){
                    ifa = getPackageName( );
                }
                Intent bi = new Intent( );
                bi.setAction(ifa);
                getBaseContext( ).sendBroadcast(bi);
                stopSelf( );
            }

            return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
        }

        @Override
        public IBinder onBind(Intent si) {
            // super.onBind(si);
            return null;
        }

        @Override
        public void onDestroy( ){
            super.onDestroy( );
            if (DEBUG) Log.i(tag, "MyService.onDestroy( );");
        }

    }

}



『<Project名>→app→main→java→com.example.widgetilstimerilsbkill→MyTimerTask(MyTimerTask.java)』(ファイルを作成)


package com.example.widgetilstimerilsbkill;

import java.util.Timer;
import java.util.TimerTask;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyTimerTask extends TimerTask {
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = AppWidget.DEBUG;
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。

    static final String fsIntentFilter_Action = AppWidget.fsIntentFilter_Action;

    static Timer timer; // = new Timer();
    SimpleDateFormat oDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");

    public MyTimerTask(Timer t){
        super();

        timer = t;
    }

    public static class MyReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context ctx, Intent ri) {
            if (DEBUG) Log.i(tag, "Receiver.onReceive( );");

            String ifa = fsIntentFilter_Action;
            if( null==ifa ){
                ifa = ctx.getPackageName();
            }
// プログラム側と「AndroidManifest.xml」の「Intent Filter 名」が一致してないような
// ヒューマン・エラー対策として Action を比較する。
            if( ifa.equals(ri.getAction( )) ) {
                if (DEBUG) Log.i(tag, "Action;");
                if(timer != null){
                    timer.cancel( );
                    timer = null;
                }
            }
        }

    }

    @Override
    public void run( ) {
        String vsDateTime = oDateFormat.format(
                new Date(System.currentTimeMillis()));
        if (DEBUG) Log.i(tag, "TimerTask : " +
                "vsDateTime=" +vsDateTime+ "; " +
                "");
    }

}



『<Project名>→app→main→res→layout→widget_main.xml』(ファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ctvTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333333"
    android:gravity="center"
    android:text="A Widget isn't launched."
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="#ffffff" />



『<Project名>→app→main→res→xml→widget_info.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_main" />
    <!-- targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を 13以下にすれば良いらしい。 -->
    <!-- 「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。 -->



『<Project名>→app→main→AndroidManifest.xml』


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

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher" >
        <receiver
            android:name=".AppWidget" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
        <service android:name=".AppWidget$MyService">
        </service>
        <receiver android:name=".MyTimerTask$MyReceiver" >
            <intent-filter>
                <action android:name="jp.kanagawa.amanojaku.WidgetILSTimerILSBKill" />
            </intent-filter>
        </receiver>
    </application>

</manifest>


<Number>: [00000A0E]  <Date>: 2016/04/16 00:46:26
<Title>: WidgetILSMTimerISLSBKill 010
<Name>: amanojaku@管理人



Widget で(Service から) 複数の Timer を起動、Broadcast による通知で Timer を停止する。
秒単位での短い間隔の繰り返しには Timer オブジェクトが適してるらしいので、ここでも Timer オブジェクトを使用する。
Widget Class 内のフィールド変数ではデータが保持されないので、Timer オブジェクトを Service Class 内のフィールド変数として定義し、Service から起動する。
Broadcast で Timer に通知して停止させる、良く分からないが Widget だと「getBaseContext( )」が使えないようで、それだと Broadcast できないと言うことになるから、結局 (Broadcast に関しては) Service から実行する必要があるようだ。
このプログラムでは MyTimerTask は Abstract(抽象) Class なので、(このプログラムで記されているとおり)必ず MyTimerTask を継承して Class を記述しなければならない。
このプログラムでは「"Alpha"、"Beta"」の2つの Timer を生成しており、Widget の起動から10秒後に Timer "Alpha"を停止させている。
TextView のデフォルト値に Text として「A Widget isn't launched.」を設定しているので、Widget を画面に設置した時に「A Widget isn't launched.」と表示されたら、Widget が起動していない事を意味する。
しばらく待てば Widget が起動するハズだが、端末が起動中の場合やデバッグ時などは Widget が起動するまで やたら時間がかかる場合がある。
つまり、実際の Widget アプリを作成する場合も、Widget が起動してない事が判るように何かメッセージを表示するとか、(メッセージを表示できない場合は) Widget が未起動時のアイコンと起動時のアイコンを切り替えて表示するとかの工夫が必要になる場合もあるでしょう。

とりあえず、[New Project]で(1ページ目)[Application name]:「WidgetILSMTimerISLSBKill」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。

targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を13以下にすれば良いらしい。
現状でインストールできる「API Level 13」以下の SDK Version の最大値は「API Level 10」(Android OS 2.3.3)となり、当方も「API Level 10」(Android OS 2.3.3)をインストールしている、よって targetSdkVersion の値は「10」としている。
なお、targetSdkVersion の設定方法は仕様が変更され「<プロジェクト名>→app→build.gradle」で設定しなければならないようだ(これらを「AndroidManifest.xml」で指定しても無視されるようだ)、「build.gradle」は同名のファイルが他にもあるようなので間違えないように注意が必要。
※必ず(Android Studio からではなく)オリジナルの「Android SDK Manager(SDK Manager.exe)」から「API Level 10」(Android OS 2.3.3)をインストールして下さい。
「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。


《参考》

Yukiの枝折: Android:Serviceの基本とonStartCommandの戻り値による ...
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html

Android Widgetのライフサイクルについて要点だけ | スマートフォン要点だけブログ
http://blog.imho.jp/2011/05/android-widget.html

Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ)
http://techbooster.org/android/application/3270/

AlarmService を利用したサービス実行のスケジューリング
http://android.keicode.com/basics/services-schedule-with-alarmmanager.php

WidgetELSProperty
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E1.+&
WidgetILSToast
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E3.+&
WidgetELActivity
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009F0.+&
WidgetELSStorage
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009FA.+&
WidgetILSTimerILSBKill
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+00000A05.+&


『<Project名>→app→main→java→com.example.widgetilsmtimerislsbkill→AppWidget(AppWidget.java)』(ファイルを作成)


package com.example.widgetilsmtimerislsbkill;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.content.ComponentName;
import android.widget.RemoteViews;
import android.util.Log;

import java.util.Timer;
import java.text.SimpleDateFormat;
import java.util.Date;

// Widget_ILSMTimer_ISLSBKill:(Widget/{(Instant Launch Service)[Multiple Timer]/({Instant,Schedule} Launch Service)[Broadcast Kill]}).
public class AppWidget extends AppWidgetProvider {
    static final String fsDebugCrest = " 010";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    SimpleDateFormat oDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");

    // (世界中で一意の「Intent Filter 名」とするため)「Intent Filter」に設定する文字列は
// パッケージ名「自分のサイトのドメイン名を逆転させた名前+アプリケーション名」を設定する事が推奨されている。
// ここではパッケージ名 自体が手抜きなので とりあえず
// 「国名+自分の大まかな出生地+ハンドル・ネーム+アプリケーション名」としている
// (出生地にしておけば引っ越しても変更する必要がない)。
// これは あくまで暫定的な一例であり、実際の「Intent Filter 名」は もっとチャントしたモノが良いだろう。
// もし、ここの「Intent Filter」を変更した場合は
// 「AndroidManifest.xml」の「Intent Filter 名」も変更しなければならない。
// 本来、「パッケージ名」自体が もっとチャントしたモノになっていれば、
// 「getPackageName( )」を使えば良いだけなだが…。
// このプログラムでは、この「fsIntentFilter_Action」変数に「null」を設定すれば、
// 「getPackageName( )」の値が使用されるようになっている。
// その場合も 当然「AndroidManifest.xml」の「Intent Filter 名」を変更しなければならない。
// ただイキナリ、世界中で一意の「Intent Filter 名」を考え出すのも難しいかもしれないので、
// とりあえず このような変数を使って試行錯誤すると言う手もある
// (試行錯誤する場合、いちいち「パッケージ名」自体を変更するのは面倒)。
    static final String fsIntentFilter_Action = "jp.kanagawa.amanojaku.WidgetILSMTimerISLSBKill";

// もし、ここで定義された Static 変数に Widget のメソッドでデータを設定したとしても、
// その(Widget の)メソッドが実行中は、当然 その Static 変数の値は保持されているだろうが、
// その後 その Static 変数の値が消失しているような感じなので、
// Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無い。
// 当然 Service 起動時に その Static 変数の値が残っている保障など無い。
// また、ここで定義された Static 変数に「Widget、Service」の
// どちらかが読み書きし そのどちらかが参照(又は読み書き)している場合、
// 競合する可能性があるので、そのような Static 変数を ここで定義してはならない。

    @Override
    public void onEnabled(Context ctx) {
        super.onEnabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onEnabled( );");

// サービスの起動を1回だけに限定したい場合は、サービスの起動を onEnabled メソッド内に記述する。
        Intent si = new Intent(ctx, MyService.class);
        ctx.startService(si);
    }

    @Override
    public void onUpdate(Context ctx, AppWidgetManager awm, int[] awid) {
        super.onUpdate(ctx, awm, awid);
        if(DEBUG)Log.i(tag, "AppWidget.onUpdate( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");
        ComponentName cn = new ComponentName(ctx, AppWidget.class);
        RemoteViews rv = new RemoteViews(ctx.getPackageName(), R.layout.widget_main);

        String vsMessage = "fsDebugCrest="+fsDebugCrest+";\n"+
                "onUpdate( );\n";
        rv.setTextViewText(R.id.ctvTextView, vsMessage);

        awm.updateAppWidget(cn, rv);
    }

    @Override
    public void onDeleted(Context ctx, int[] awid) {
        super.onDeleted(ctx, awid);
        if (DEBUG) Log.i(tag, "AppWidget.onDeleted( );");
    }

    @Override
    public void onDisabled(Context ctx){
        super.onDisabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onDisabled( );");

// Timer の名前を指定してない場合は、現在 生存している Timer を全て Kill し、
// Service も終了させている。
        Intent si = new Intent(ctx, MyService.class);
        si.putExtra("KillKey", Boolean.valueOf(true)); // false; //
        ctx.startService(si);
        /*
        Intent si = new Intent(ctx, MyService.class);
        ctx.stopService(si);
        */
    }

    public static class MyService extends Service {

        // 下記「timer、delay、interval」は あくまでもテンポラリー。
        Timer timer; // = new Timer( );
        long delay; // ms
        long interval; // ms

        @Override
        public void onCreate( ){
            super.onCreate( );
            if (DEBUG) Log.i(tag, "MyService.onCreate( );");
        }

        @Override
        public int onStartCommand(Intent si, int f, int sid) {
            super.onStartCommand(si, f, sid);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( );");

            AppWidgetManager awm = AppWidgetManager.getInstance(this);
            ComponentName cn = new ComponentName(this, AppWidget.class);
            RemoteViews rv = new RemoteViews(getPackageName(), R.layout.widget_main);

            String vsMessage = "MyService.onStartCommand( );\n";
            rv.setTextViewText(R.id.ctvTextView, vsMessage);

            awm.updateAppWidget(cn, rv);

            Boolean oKill = si.getBooleanExtra("KillKey", false);
            if( ! oKill.booleanValue( ) ){
                if(DEBUG)Log.i(tag, "MyTimerTask( );");

                delay = 0; // 0ms
                interval = 1000; // 1000ms
                timer = new Timer( );
                timer.scheduleAtFixedRate(new MyTimerTask(timer, "Alpha") {
                    @Override
                    public void run() {
                        String vsDateTime = oDateFormat.format(
                                new Date(System.currentTimeMillis()));
                        if (DEBUG) Log.i(tag, "TimerTask : " +
                                "vsName=" + vsName + "; " +
                                "vsDateTime=" + vsDateTime + "; " +
                                "");
                    }
                }, delay, interval);

                delay = 0; // 0ms
                interval = 2000; // 2000ms
                timer = new Timer( );
                timer.scheduleAtFixedRate(new MyTimerTask(timer, "Beta") {
                    @Override
                    public void run() {
                        String vsDateTime = oDateFormat.format(
                                new Date(System.currentTimeMillis( )));
                        if (DEBUG) Log.i(tag, "TimerTask : " +
                                "vsName=" + vsName + "; " +
                                "vsDateTime=" + vsDateTime + "; " +
                                "");
                    }
                }, delay, interval);

// 10秒後に自分自身(MyService.onStartCommand())を呼び出して Timer "Alpha"を Kill する。
                delay = 10*1000L; // 10s
                Intent asi = new Intent(this, this.getClass( ));
                asi.putExtra("TimerKey", "Alpha");
                asi.putExtra("KillKey", Boolean.valueOf(true)); // false; //
                PendingIntent aspi = PendingIntent.getService(
                        this, 0, asi, 0);
                AlarmManager am = (AlarmManager)this.getSystemService(Context.ALARM_SERVICE);
                am.set(AlarmManager.RTC, System.currentTimeMillis( )+delay, aspi);
            }else{
                if(DEBUG)Log.i(tag, "Kill;");
                String vsTimer = si.getStringExtra("TimerKey");
                String ifa = fsIntentFilter_Action;
                if( null==ifa ){
                    ifa = getPackageName( );
                }
                Intent bi = new Intent( );
                bi.setAction(ifa);
                bi.putExtra("TimerKey", vsTimer);
                getBaseContext( ).sendBroadcast(bi);
                if( null==vsTimer || "".equals(vsTimer) ){
                    stopSelf( );
                }
            }

            return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
        }

        @Override
        public IBinder onBind(Intent si) {
            // super.onBind(si);
            return null;
        }

        @Override
        public void onDestroy( ){
            super.onDestroy( );
            if (DEBUG) Log.i(tag, "MyService.onDestroy( );");
        }

    }

}



『<Project名>→app→main→java→com.example.widgetilsmtimerislsbkill→MyTimerTask(MyTimerTask.java)』(ファイルを作成)


package com.example.widgetilsmtimerislsbkill;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import java.text.SimpleDateFormat;
import java.util.Date;

public abstract class MyTimerTask extends TimerTask {
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = AppWidget.DEBUG;
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。

    static final String fsIntentFilter_Action = AppWidget.fsIntentFilter_Action;

    // static Timer timer; // = new Timer();
    // int iID = -1;
    String vsName = "";
    // static List<String> di1vsName; // = new ArrayList<String>( );
    // static List<Timer> di1oTimer; // = new ArrayList<Timer>( );
    static HashMap<String, Timer> dm1oTimer; // = new HashMap<String, Timer>( );

    SimpleDateFormat oDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");

    public MyTimerTask(Timer t, String vsN){
        super();
// Timer の同名の多重登録はチェックしていません。
        vsName = vsN;

        if( null==dm1oTimer ){
            dm1oTimer = new HashMap<String, Timer>( );
        }
        dm1oTimer.put(vsName, t);
        if(DEBUG)Log.i(tag, "MyTimerTask( ) : "+
                "vsName="+vsName+"; "+
                "dm1oTimer.size( )="+dm1oTimer.size( )+"; "+
                "");
    }

    public static class MyReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context ctx, Intent ri) {
            if (DEBUG) Log.i(tag, "Receiver.onReceive( );");

            String ifa = fsIntentFilter_Action;
            if( null==ifa ){
                ifa = ctx.getPackageName();
            }
// プログラム側と「AndroidManifest.xml」の「Intent Filter 名」が一致してないような
// ヒューマン・エラー対策として Action を比較する。
            if( ifa.equals(ri.getAction( )) ) {
                if (DEBUG) Log.i(tag, "Action;");
                if( null!=dm1oTimer ){
                    Timer timer;
                    String vsTimer = ri.getStringExtra("TimerKey");
                    if( null!=vsTimer && ! vsTimer.equals("") ){
                        timer = dm1oTimer.get(vsTimer);
                        if( null!=timer ){
                            timer.cancel();
                            dm1oTimer.remove(vsTimer);
                        }
                    }else {
                        String k;
                        String[] key = dm1oTimer.keySet().toArray(new String[0]);
                        for (int i = 0; i < key.length; i++) {
// ↑どうも Android では「for( String k : dm1oTimer.keySet( ) )」のような「拡張 for 文」や、
// Iterator なども実行時エラーになるようだ。
                            k = key[i];
                            if (DEBUG) Log.i(tag, "Receiver.onReceive( ) : " +
                                    "i=" + i + "; " +
                                    "k=" + k + "; " +
                                    "");
                            timer = dm1oTimer.get(k);
                            if (null != timer) {
                                timer.cancel();
                                dm1oTimer.remove(k);
                            }
                        }
                    }
                }
            }
        }

    }

}



『<Project名>→app→main→res→layout→widget_main.xml』(ファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ctvTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333333"
    android:gravity="center"
    android:text="A Widget isn't launched."
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="#ffffff" />



『<Project名>→app→main→res→xml→widget_info.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_main" />
    <!-- targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を 13以下にすれば良いらしい。 -->
    <!-- 「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。 -->



『<Project名>→app→main→AndroidManifest.xml』


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

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher" >
        <receiver
            android:name=".AppWidget" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
        <service android:name=".AppWidget$MyService">
        </service>
        <receiver android:name=".MyTimerTask$MyReceiver" >
            <intent-filter>
                <action android:name="jp.kanagawa.amanojaku.WidgetILSMTimerISLSBKill" />
            </intent-filter>
        </receiver>
    </application>

</manifest>


<Number>: [00000A19]  <Date>: 2016/04/12 02:40:49
<Title>: Widget_ILFS_Toast
<Name>: amanojaku@管理人



(Widget から起動している) Service を Foreground に設定してやり、Service を死ににくくする。
Service を Foreground に設定すると、(通知バーには表示されない場合があるが、通知バーを下ろした)通知領域にアイコンが表示される、場合によっては通知バーにもアイコンが表示される(良く分からないが そう言う仕様なのか?)。
画面から Widget が削除されると、 Service も終了されるが、その時に Foreground も解除されるので、通知領域のアイコンも消える。
TextView のデフォルト値に Text として「A Widget isn't launched.」を設定しているので、Widget を画面に設置した時に「A Widget isn't launched.」と表示されたら、Widget が起動していない事を意味する。
しばらく待てば Widget が起動するハズだが、端末が起動中の場合やデバッグ時などは Widget が起動するまで やたら時間がかかる場合がある。
つまり、実際の Widget アプリを作成する場合も、Widget が起動してない事が判るように何かメッセージを表示するとか、(メッセージを表示できない場合は) Widget が未起動時のアイコンと起動時のアイコンを切り替えて表示するとかの工夫が必要になる場合もあるでしょう。

Service が実行されると Toast を表示する。
通知領域のアイコンをタップしても Service が実行され Toast が表示される。

とりあえず、[New Project]で(1ページ目)[Application name]:「Widget_ILFS_Toast」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。

一応、通知用アイコンを Android のマスコット・キャラの「Droid君」にしている。
通知用アイコンは白色が推奨されているらしいので、一部 白ではないが白を基調としている。
下の方に"drawable-hdpi"フォルダー用の画像ファイル(「36x36」Pixel)をアップしてあります(「drawable-hdpi」フォルダーが存在しない場合は作成してやる)。
"drawable-mdpi"フォルダー用は手抜きして作ってないが、解像度が低い端末でも自動的に その端末の解像度に合うように縮小して表示される。

targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を13以下にすれば良いらしい、よって targetSdkVersion の値は「13」としている。
なお、targetSdkVersion の設定方法は仕様が変更され「<プロジェクト名>→app→build.gradle」で設定しなければならないようだ(これらを「AndroidManifest.xml」で指定しても無視されるようだ)、「build.gradle」は同名のファイルが他にもあるようなので間違えないように注意が必要。
※「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。


《参考》

Yukiの枝折: Android:Serviceの基本とonStartCommandの戻り値による ...
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html

Android Widgetのライフサイクルについて要点だけ | スマートフォン要点だけブログ
http://blog.imho.jp/2011/05/android-widget.html

Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ)
http://techbooster.org/android/application/3270/

AlarmService を利用したサービス実行のスケジューリング
http://android.keicode.com/basics/services-schedule-with-alarmmanager.php

WidgetELSProperty
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E1.+&
WidgetILSToast
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E3.+&
WidgetELActivity
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009F0.+&
WidgetELSStorage
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009FA.+&
WidgetILSTimerILSBKill
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+00000A05.+&
WidgetILSMTimerISLSBKill
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+00000A0E.+&


『<Project名>→app→main→java→com.example.widget_ilfs_toast→AppWidget(AppWidget.java)』(ファイルを作成)


package com.example.widget_ilfs_toast;

import android.app.PendingIntent;
import android.app.Service;
import android.app.Notification;
import android.support.v7.app.NotificationCompat;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.content.ComponentName;
import android.view.Gravity;
import android.widget.RemoteViews;
import android.util.Log;
import android.widget.Toast;

// Widget_ILFS_Toast(Widget/{(Instant Launch Foreground Service)/(Toast)}).
public class AppWidget extends AppWidgetProvider {
    static final String fsDebugCrest = " 010";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    static final int ONGOING_NOTIFICATION = 1;

// もし、ここで定義された Static 変数に Widget のメソッドでデータを設定したとしても、
// その(Widget の)メソッドが実行中は、当然 その Static 変数の値は保持されているだろうが、
// その後 その Static 変数の値が消失しているような感じなので、
// Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無い。
// 当然 Service 起動時に その Static 変数の値が残っている保障など無い。
// また、ここで定義された Static 変数に「Widget、Service」の
// どちらかが読み書きし そのどちらかが参照(又は読み書き)している場合、
// 競合する可能性があるので、そのような Static 変数を ここで定義してはならない。

    @Override
    public void onEnabled(Context ctx) {
        super.onEnabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onEnabled( );");
    }

    @Override
    public void onUpdate(Context ctx, AppWidgetManager awm, int[] awid) {
        super.onUpdate(ctx, awm, awid);
        if(DEBUG)Log.i(tag, "AppWidget.onUpdate( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");

        Intent si = new Intent(ctx, MyService.class);
        ctx.startService(si);
    }

    @Override
    public void onDeleted(Context ctx, int[] awid) {
        super.onDeleted(ctx, awid);
        if (DEBUG) Log.i(tag, "AppWidget.onDeleted( );");
    }

    @Override
    public void onDisabled(Context ctx){
        super.onDisabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onDisabled( );");

        Intent si = new Intent(ctx, MyService.class);
        ctx.stopService(si);
    }

    public static class MyService extends Service {

        @Override
        public void onCreate( ){
            super.onCreate();
            if (DEBUG) Log.i(tag, "MyService.onCreate( );");
        }

        @Override
        public int onStartCommand(Intent si, int f, int sid) {
            super.onStartCommand(si, f, sid);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( );");

// 良く分からないが、次の PendingIntent は
// Activity の取得の場合は「getActivity(〜)」、
// Activity の取得 以外は「getService(〜)」になると思われる。
            SettingForeground(PendingIntent.getService(
                    this, 0, si, PendingIntent.FLAG_CANCEL_CURRENT));

            AppWidgetManager awm = AppWidgetManager.getInstance(this);
            ComponentName cn = new ComponentName(this, AppWidget.class);
            RemoteViews rv = new RemoteViews(getPackageName(), R.layout.widget_main);

            String vsMessage = "fsDebugCrest="+fsDebugCrest+";\n"+
                    "MyService.onStartCommand( );";
            rv.setTextViewText(R.id.ctvTextView, vsMessage);

            awm.updateAppWidget(cn, rv);

            Toast.makeText(getApplicationContext( ), vsMessage, Toast.LENGTH_SHORT).show();

            return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
        }

        @Override
        public IBinder onBind(Intent si) {
            // super.onBind(si);
            return null;
        }

        public void SettingForeground(PendingIntent pi) {
            if(DEBUG)Log.i(tag, "MyService.SettingForeground( );");
            Notification notif = new NotificationCompat.Builder(this)
                    .setContentIntent(pi)
                    .setAutoCancel(true)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("Content Text.")
                    // .setContentInfo("Content Info.")
                    // .setTicker("Ticker.")
                    .setWhen(System.currentTimeMillis())
                    .setSmallIcon(R.drawable.x_slogo_android031) // SmallIcon を設定しないと通知されない(例外も発生しない)
                    .build( );
            startForeground(ONGOING_NOTIFICATION, notif);
        }

        @Override
        public void onDestroy( ){
            super.onDestroy( );
            if (DEBUG) Log.i(tag, "MyService.onDestroy( );");
            stopForeground(true);
        }

    }

}



『<Project名>→app→main→res→layout→widget_main.xml』(ファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ctvTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333333"
    android:gravity="center"
    android:text="A Widget isn't launched."
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="#ffffff" />



『<Project名>→app→main→res→xml→widget_info.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_main" />
    <!-- targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を 13以下にすれば良いらしい。 -->
    <!-- 「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。 -->



『<Project名>→app→main→AndroidManifest.xml』


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

    <application
        android:allowBackup="true"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher" >
        <receiver
            android:name=".AppWidget" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
        <service android:name=".AppWidget$MyService">
        </service>
    </application>

</manifest>


<Number>: [00000A1E]  <Date>: 2016/04/12 08:25:11
<Title>: Widget_ILFS_NELAClick
<Name>: amanojaku@管理人



通知領域のアイコンをタップしてやると Activity を起動する(「Widget_ILFS_NELAClick、WidgetELActivity」を元に作成)。
(Widget から起動している) Service を Foreground に設定してるので、Service が死ににくくなっている。
Service を Foreground に設定すると、(通知バーには表示されない場合があるが、通知バーを下ろした)通知領域にアイコンが表示される、場合によっては通知バーにもアイコンが表示される(良く分からないが そう言う仕様なのか?)。
画面から Widget が削除されると、 Service も終了されるが、その時に Foreground も解除されるので、通知領域のアイコンも消える。
TextView のデフォルト値に Text として「A Widget isn't launched.」を設定しているので、Widget を画面に設置した時に「A Widget isn't launched.」と表示されたら、Widget が起動していない事を意味する。
しばらく待てば Widget が起動するハズだが、端末が起動中の場合やデバッグ時などは Widget が起動するまで やたら時間がかかる場合がある。
つまり、実際の Widget アプリを作成する場合も、Widget が起動してない事が判るように何かメッセージを表示するとか、(メッセージを表示できない場合は) Widget が未起動時のアイコンと起動時のアイコンを切り替えて表示するとかの工夫が必要になる場合もあるでしょう。

通知領域のアイコンをタップしてやると Activity を起動する。
Widget をタップしても Activity を起動する。

とりあえず、[New Project]で(1ページ目)[Application name]:「Widget_ILFS_NELAClick」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。

「drawable-hdpi」フォルダー(存在しない場合は作成してやる)内に「Android SDK」のデフォルト・アイコン「ic_menu_moreoverflow_normal_holo_light.png」をコピーする。
一応、「アプリの Logo マーク、通知用アイコン」を Android のマスコット・キャラの「Droid君」にしている。
通知用アイコンは白色が推奨されているらしいので、(一部 白ではないが)白を基調としている。
下の方に"drawable-hdpi"フォルダー用の画像ファイル(アプリの Logo マーク:「48x48」Pixel、通知用アイコン:「36x36」Pixel)をアップしてあります。
"drawable-mdpi"フォルダー用は手抜きして作ってないが、解像度が低い端末でも自動的に その端末の解像度に合うように縮小して表示される。
その場合、当然 画質は悪くなるので画質を綺麗にしたい場合は手抜きせずにチャント"drawable-mdpi"フォルダー用の画像ファイル(「32x32」Pixel)を作成してやれば良い。
なお、Activity の Toolbar の UI はスタンダードな「ActionBarActivity」のような感じになっている。

targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を13以下にすれば良いらしい、よって targetSdkVersion の値は「13」としている。
なお、targetSdkVersion の設定方法は仕様が変更され「<プロジェクト名>→app→build.gradle」で設定しなければならないようだ(これらを「AndroidManifest.xml」で指定しても無視されるようだ)、「build.gradle」は同名のファイルが他にもあるようなので間違えないように注意が必要。
※「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。


《参考》

Yukiの枝折: Android:Serviceの基本とonStartCommandの戻り値による ...
http://yuki312.blogspot.jp/2012/07/androidserviceonstartcommand.html

Android Widgetのライフサイクルについて要点だけ | スマートフォン要点だけブログ
http://blog.imho.jp/2011/05/android-widget.html

Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ)
http://techbooster.org/android/application/3270/

AlarmService を利用したサービス実行のスケジューリング
http://android.keicode.com/basics/services-schedule-with-alarmmanager.php

WidgetELSProperty
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E1.+&
WidgetILSToast
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009E3.+&
WidgetELActivity
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009F0.+&
WidgetELSStorage
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009FA.+&
WidgetILSTimerILSBKill
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+00000A05.+&
WidgetILSMTimerISLSBKill
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+00000A0E.+&
Widget_ILFS_Toast
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+00000A19.+&


『<Project名>→app→main→java→com.example.widget_ilfs_nelaclick→AppWidget(AppWidget.java)』(ファイルを作成)


package com.example.widget_ilfs_nelaclick;

import android.app.PendingIntent;
import android.app.Service;
import android.app.Notification;
import android.support.v7.app.NotificationCompat;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.content.ComponentName;
import android.widget.RemoteViews;
import android.util.Log;

// Widget_ILFS_NELAClick(appWidget/{(Instant Launch Foreground Service)/(status Notifications area):(intent Event Launch Activity):(Click)}).
public class AppWidget extends AppWidgetProvider {
    static final String fsDebugCrest = " 011";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    static final int ONGOING_NOTIFICATION = 1;

// もし、ここで定義された Static 変数に Widget のメソッドでデータを設定したとしても、
// その(Widget の)メソッドが実行中は、当然 その Static 変数の値は保持されているだろうが、
// その後 その Static 変数の値が消失しているような感じなので、
// Widget の他のメソッド実行時にさえ その Static 変数の値が残っている保障は無い。
// 当然 Service 起動時に その Static 変数の値が残っている保障など無い。
// また、ここで定義された Static 変数に「Widget、Service」の
// どちらかが読み書きし そのどちらかが参照(又は読み書き)している場合、
// 競合する可能性があるので、そのような Static 変数を ここで定義してはならない。

    @Override
    public void onEnabled(Context ctx) {
        super.onEnabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onEnabled( );");
    }

    @Override
    public void onUpdate(Context ctx, AppWidgetManager awm, int[] awid) {
        super.onUpdate(ctx, awm, awid);
        if(DEBUG)Log.i(tag, "AppWidget.onUpdate( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");

        Intent si = new Intent(ctx, MyService.class);
        ctx.startService(si);

// MyService で AppWidget の rv(R.layout.widget_main) を上書きしてしまうので、
// AppWidget での rv に対する処理は無意味ですが、
// AppWidget と MyService ではビミョウに違がっているので、
// そのビミョウな違いのサンプルとして参考にして下さい。
        ComponentName cn = new ComponentName(ctx, AppWidget.class);
        RemoteViews rv = new RemoteViews(ctx.getPackageName(), R.layout.widget_main);

        String vsMessage = "fsDebugCrest="+fsDebugCrest+";\n"+
                "AppWidget.onUpdate( );";
        rv.setTextViewText(R.id.ctvTextView, vsMessage);

        // 起動するアクティビティの指定
        PendingIntent pi = PendingIntent.getActivity(
                ctx, 0, new Intent(ctx, MainActivity.class), 0);
        rv.setOnClickPendingIntent(R.id.ctvTextView, pi);

        // awm.updateAppWidget により前述の2つの「rv.set〜」の設定が Update される。
        awm.updateAppWidget(cn, rv);
    }

    @Override
    public void onDeleted(Context ctx, int[] awid) {
        super.onDeleted(ctx, awid);
        if (DEBUG) Log.i(tag, "AppWidget.onDeleted( );");
    }

    @Override
    public void onDisabled(Context ctx){
        super.onDisabled(ctx);
        if (DEBUG) Log.i(tag, "AppWidget.onDisabled( );");

        Intent si = new Intent(ctx, MyService.class);
        ctx.stopService(si);
    }


    public static class MyService extends Service {

        @Override
        public void onCreate( ){
            super.onCreate( );
            if (DEBUG) Log.i(tag, "MyService.onCreate( );");
        }

        @Override
        public int onStartCommand(Intent si, int f, int sid) {
            super.onStartCommand(si, f, sid);
            if(DEBUG)Log.i(tag, "MyService.onStartCommand( );");

// 良く分からないが、次の PendingIntent は
// Activity の取得の場合は「getActivity(〜)」、
// Activity の取得 以外は「getService(〜)」になると思われる。
            SettingForeground(PendingIntent.getActivity(
                    this, 0, new Intent(this, MainActivity.class),
                    PendingIntent.FLAG_CANCEL_CURRENT));

// MyService で AppWidget の rv(R.layout.widget_main) を上書きしてしまうので、
// AppWidget での rv に対する処理は無意味ですが、
// AppWidget と MyService ではビミョウに違がっているので、
// そのビミョウな違いのサンプルとして参考にして下さい。
            AppWidgetManager awm = AppWidgetManager.getInstance(this);
            ComponentName cn = new ComponentName(this, AppWidget.class);
            RemoteViews rv = new RemoteViews(getPackageName(), R.layout.widget_main);

            String vsMessage = "fsDebugCrest="+fsDebugCrest+";\n"+
                     "MyService.onStartCommand( );";
            rv.setTextViewText(R.id.ctvTextView, vsMessage);

            // 起動するアクティビティの指定
            PendingIntent pi = PendingIntent.getActivity(
                    this, 0, new Intent(this, MainActivity.class), 0);
            rv.setOnClickPendingIntent(R.id.ctvTextView, pi);

            awm.updateAppWidget(cn, rv);

            return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
        }

        @Override
        public IBinder onBind(Intent si) {
            // super.onBind(si);
            return null;
        }

        public void SettingForeground(PendingIntent pi) {
            if(DEBUG)Log.i(tag, "MyService.SettingForeground( );");
            Notification notif = new NotificationCompat.Builder(this)
                    .setContentIntent(pi)
                    .setAutoCancel(true)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("Content Text.")
                    // .setContentInfo("Content Info.")
                    // .setTicker("Ticker.")
                    .setWhen(System.currentTimeMillis())
                    .setSmallIcon(R.drawable.x_slogo_android031) // SmallIcon を設定しないと通知されない(例外も発生しない)
                    .build( );
            startForeground(ONGOING_NOTIFICATION, notif);
        }

        @Override
        public void onDestroy( ){
            super.onDestroy( );
            if (DEBUG) Log.i(tag, "MyService.onDestroy( );");
            stopForeground(true);
        }

    }

}



『<Project名>→app→main→res→layout→widget_main.xml』(ファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ctvTextView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#333333"
    android:gravity="center"
    android:text="A Widget isn't launched."
    android:textAppearance="?android:attr/textAppearanceMedium"
    android:textColor="#ffffff" />



『<Project名>→app→main→res→xml→widget_info.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dip"
    android:minHeight="72dip"
    android:updatePeriodMillis="0"
    android:initialLayout="@layout/widget_main" />
    <!-- targetSdkVersion が14以上だと Widget のサイズの計算法が変わるそうなので、(Widget の作成時)古い Version と互換性を持たせたい場合は targetSdkVersion を 13以下にすれば良いらしい。 -->
    <!-- 「API Level 13」以下の Widget サイズの計算法は「74dip × セル数 - 2dip」となるらしい。 -->


『<Project名>→app→main→java→com.example.widget_ilfs_nelaclick→MainActivity(MainActivity.java)』


package com.example.widget_ilfs_nelaclick;

import android.os.Bundle;
import android.util.Log;
import android.app.Activity;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
// import android.widget.Toolbar;
import android.content.Context;

import android.view.View;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import android.widget.TextView;
import android.view.KeyEvent;

import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;

public class MainActivity extends AppCompatActivity
        implements View.OnClickListener {
    static final String fsDebugCrest = " 008";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。
    MainActivity oMainApp;
    Context oBaseContext;
    Context oAppContext;
    Context oContext;
    ActionBar oActionBar;
    Menu oMainMenu;

    String vsPackageName;
    String vsVersionName;
    int iVersionCode;
    String vsToolbarTitle;
    static final int[] WIDGETS =
            new int[]{R.id.button1, R.id.checkBox1, R.id.toggleButton1, R.id.radioButton1};
    TextView ctvMessage;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if(DEBUG)Log.i(tag, "Activity.onCreate( );");
        setContentView(R.layout.activity_main);
        oMainApp = this;
// System 共通の Context:別の Application とやりとりするとき。
        oBaseContext = getBaseContext( );
// Application 固有の Context(Application ごとに Context が変化する):Application 共通のモノが対象。
        oAppContext = getApplicationContext( );
// Activity 固有の Context(Activity ごとに Context が変化する):Activity に依存モノが対象。
        oContext = this;

        vsPackageName = getPackageName( );
        try {
            PackageInfo packageInfo = this.getPackageManager( ).getPackageInfo(
                    this.getPackageName( ), PackageManager.GET_META_DATA );
            // 「<Project名>→app→build.gradle」ファイル内の
            // 「versionName、versionCode」の設定値を取得。
            vsVersionName = packageInfo.versionName;
            iVersionCode = packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            // 例外処理
            e.printStackTrace( );
        }

        ctvMessage = (TextView)findViewById(R.id.message);
        ctvMessage.append(
                "PackageName=" + vsPackageName + ";\n" +
                        "VersionName=" + vsVersionName + ";\n" +
                        "BuildNo=" + String.valueOf(iVersionCode) + ";\n" +
                        "");

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        oActionBar = getSupportActionBar();

        // oActionBar.setIcon(R.drawable.ic_menu_info_details);
        oActionBar.setLogo(R.drawable.x_logo_android022);
        // ↑ 実際は Logo マークはアプリ固有の Icon にすべきモノです。
        // アプリ固有の Logo マークと言われてもイメージが湧かない方は
        // ブラウザの Icon(Logo マーク)をイメージしてみて下さい。
        // 下記サイトに主要ブラウザの Icon(Logo マーク)が掲載されています。
        // http://freesoft-100.com/pasokon/browser.html
        // ちなみに Toolbar に表示する Icon サイズのスタンダードは
        // "drawable-mdpi"フォルダー用は「32x32」、
        // "drawable-hdpi"フォルダー用は「48x48」だと思われます。

        vsToolbarTitle = "  "+getString(R.string.app_name)+
                fsDebugCrest+
                " V"+vsVersionName+ // Version.
                " R"+String.valueOf(iVersionCode); // Revision.
// ↑ Toolbar に Logo マークを表示させるとタイトルと Logo マークとの間に隙間がないので、
// タイトルの先頭に半角スペースを2つ入れている。
        oActionBar.setTitle(vsToolbarTitle);

        for (int iWgId : WIDGETS) {
            View cvw = findViewById(iWgId);
            cvw.setOnClickListener(this);
        }
    }

    @Override
    protected void onRestart( ) {
        super.onRestart();
        if(DEBUG)Log.i(tag, "Activity.onRestart( );");
    }

    @Override
    protected void onStart( ) {
        super.onStart();
        if(DEBUG)Log.i(tag, "Activity.onStart( );");
    }

    @Override
    protected void onResume( ) {
        super.onResume();
        if(DEBUG)Log.i(tag, "Activity.onResume( );");
    }

    @Override
    protected void onPause( ) {
        super.onPause();
        if(DEBUG)Log.i(tag, "Activity.onPause( );");
    }

    @Override
    protected void onStop( ) {
        super.onStop();
        if(DEBUG)Log.i(tag, "Activity.onStop( );");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(DEBUG)Log.i(tag, "Activity.onDestroy( );");
    }

    @Override
    public void onClick(View v) {
        // super.onClick(v);
        if(DEBUG) Log.i(tag,"Activity.onClick( );");
        switch(v.getId()) {
            case R.id.button1 :
                if (DEBUG) Log.i(tag, "button1");

                break;
            case R.id.checkBox1 :
                if (DEBUG) Log.i(tag, "checkBox1");

                break;
            case R.id.toggleButton1 :
                if (DEBUG) Log.i(tag, "toggleButton1");

                break;
            case R.id.radioButton1 :
                if (DEBUG) Log.i(tag, "radioButton1");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
                return;
        }
        return;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        // アクションバー内で使用する為のメニューアイテムを実体化
        MenuInflater inflater = getMenuInflater();
        // MenuInflater inflater = getSupportMenuInflater();
        inflater.inflate(R.menu.main, menu);
        // 「R.menu.main」の「menu.main」は 恐らく「res」からの「フォルダー名+ファイル名」
        oMainMenu = menu;
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        if(DEBUG) Log.i(tag,"Activity.onOptionsItemSelected( );");
        switch(item.getItemId()) {
            case R.id.menu_main_settings :
                if (DEBUG) Log.i(tag, "menu_main_settings");

                break;
            case R.id.menu_main_settings_item1 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item1");

                break;
            case R.id.menu_main_settings_item2 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item2");

                break;
            case R.id.menu_main_settings_item3 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item3");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                return false; // true; //
        }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        super.dispatchKeyEvent(event);
        if(DEBUG) Log.i(tag,"Activity.dispatchKeyEvent( );");
        final int action = event.getAction();
        final int keyCode = event.getKeyCode();
        switch(keyCode) {
            case KeyEvent.KEYCODE_MENU :
                if(DEBUG) Log.i(tag,"KeyEvent.KEYCODE_MENU;");
                if ( action==KeyEvent.ACTION_UP ) {
                    if(DEBUG) Log.i(tag,"KeyEvent.ACTION_UP;");
                    if ( oMainMenu!=null ) {
                        if(DEBUG) Log.i(tag,"oMainMenu.performIdentifierAction( );");
                        oMainMenu.performIdentifierAction(R.id.menu_main_settings, 0);
                    }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
                    return true; // false; //
                }
                break;

            default :
                if (DEBUG) Log.i(tag, "KeyEvent:Default");

                break;
        }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
        return false; // true; //
    }

}



『<Project名>→app→main→res→drawable→border.xml』(ファイルを作成)
※「TextView」の枠として使っている。


<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ffffff" />
    <padding android:top="3dp" android:bottom="3dp"
        android:left="7dp" android:right="7dp" />
    <stroke android:width="1px" android:color="#000000" />
    <corners android:radius="5dp" />
</shape>



『<Project名>→app→main→res→layout→activity_main.xml』


<?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"
    tools:context=".MainActivity"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/actionBarSize"
        android:background="#9b7fff"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <TextView
        android:id="@+id/message"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_margin = "7dp"
        android:background="@drawable/border"
        android:textSize="@dimen/abc_text_size_medium_material"
        android:layout_weight="1" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button 1"
        android:id="@+id/button1"
        android:layout_gravity="center_horizontal" />

    <ToggleButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ToggleButton 1"
        android:id="@+id/toggleButton1"
        android:layout_gravity="center_horizontal" />

    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="CheckBox 1"
        android:id="@+id/checkBox1"
        android:layout_gravity="center_horizontal" />

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="RadioButton 1"
        android:id="@+id/radioButton1"
        android:layout_gravity="center_horizontal" />

</LinearLayout>



『<Project名>→app→src→main→res→menu→main.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<menu
    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"
    tools:context=".MainActivity">

    <item android:id="@+id/menu_main_settings"
        android:title="Settings"
        android:icon="@drawable/ic_menu_moreoverflow_normal_holo_light"
        app:showAsAction="always">
        <!-- ↑ showAsAction:Toolbar に対する表示設定。 -->
        <!-- "always":常に表示、"never":常に表示しない、"ifRoom":表示する余裕があれば表示。 -->
        <menu>
            <item android:id="@+id/menu_main_settings_item1"
                android:title="item1">
            </item>

            <item android:id="@+id/menu_main_settings_item2"
                android:title="item2">
            </item>

            <item android:id="@+id/menu_main_settings_item3"
                android:title="item3">
            </item>
        </menu>
    </item>

</menu>



『<Project名>→app→src→main→res→values→styles.xml』


<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>



『<Project名>→app→main→AndroidManifest.xml』


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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
            android:name=".AppWidget"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
            </intent-filter>
            <meta-data
                android:name="android.appwidget.provider"
                android:resource="@xml/widget_info" />
        </receiver>
        <service android:name=".AppWidget$MyService">
        </service>
        <activity android:name=".MainActivity" >
        </activity>
    </application>

</manifest>


<Number>: [00000A23]  <Date>: 2016/04/27 18:15:52
<Title>: KitchenTimer 026
<Name>: amanojaku@管理人



死ににくい Timer を作る。
Activity は Background になると意外と簡単に死ぬので、Activity が Background になっても死ににくい Timer を作る。
Timer 起動時に Foreground 化した Service を起動し、死ににくい Service に Timer を Anchor する事で Timer を死ににくくする。
それでも Background  時の Activity 自体が死にやすい事に変わりはないのだが、例え Activity が死んでも Timer は生存して Alarm が正常に鳴る(この Timer は Foreground 化した Service と同等の死ににくさを持っている)。
Timer 起動時に Foreground 化した Service により、(通知バーには表示されない場合があるが、通知バーを下ろした)通知領域にアイコンが表示される、その通知領域にアイコンをタップすると Activity が起動する(テストしたい場合は Timer を少し長めにセットして下さい)。
Timer が終了すると Foreground 化した Service も終了し、通知領域のアイコンも消える。

音源は下記「げきばん!」様からダウンロードいたしました。
フリー作品を使用する場合の注意点。
"利用規約"に違反した場合は違法となり罰金などが請求される可能性がありますので、必ず そのサイトの"利用規約"を お読み下さい。
(通常は)著作権を放棄しているものではありません、(通常は)再配布は禁止されています。
(大抵は)商用として使用できません、(大抵は)改変が禁止されています。
(場合によっては)著作権表示が義務付けられている事があります。

げきばん! オリジナルサウンドトラック制作(音楽・効果音・声・MA)
http://soundjewel.symphie.jp/product/gekiban/free/

「げきばん!」様の"利用規約"。

> すべて無料でダウンロードできます
> 商用としてご使用できます
> ご申告は不要です
> JASRACなどへのご申告も不要です
> 著作権表示、作者名のクレジットは不要です
> 編集やエフェクトを加えるなど、改変してご使用できます
> 音楽ファイルを販売するなど、著作権を侵害する使用はできません

再配布については書かれていませんが、(再配布は禁止されているのが通常なので)再配布は禁止と考えた方が無難でしょう。

当方がダウンロードしたファイルは「真夜中のメリーゴーランド:Mayonaka_No_Merry-Go-Round.wav」(変換後のファイル:mayonaka_no_merrygoround.mp3)、「真夜中のメリーゴーランド(トイピアノ):Mayonaka_No_Merry-Go-Round_Toy_Piano.wav」(変換後のファイル:mayonaka_no_merrygoround_toypiano.mp3)、「真夜中のメリーゴーランド(ハープ):Mayonaka_No_Merry-Go-Round_Harp.wav」(変換後のファイル:mayonaka_no_merrygoround_harp.mp3)、「夏の残り香(ピアノ):Natsu_No_Nokoriga_Piano.wav」(変換後のファイル:natsu_no_nokoriga_piano.mp3)、「新しい道:Atarashii_Michi.wav」(変換後のファイル:atarashii_michi.mp3)です。
※「げきばん!」様のサイト内・検索で これらのファイルを検索したい場合は、上記の日本語名で検索して下さい。
※ 音源の変換は「XMedia Recode Version 3.2.6.1」(フリー・ソフト)を使用しています。
※ Android の Resource で使用可能なファイル名は「英小文字、数字、アンダースコア」のみで「英大文字」、「ハイフンや その他の記号」なども使用できないようなので、(データ形式の変換だけでなく)ファイル名も変更しています。
本プログラムで使用する場合は「変換後のファイル」として記述されているファイル名と完全に一致させて下さい。
『<Project名>→app→main→res→raw』フォルダー(存在しない場合は作成してやる)内に上記の「mayonaka_no_merrygoround.mp3、mayonaka_no_merrygoround_toypiano.mp3、mayonaka_no_merrygoround_harp.mp3、natsu_no_nokoriga_piano.mp3、atarashii_michi.mp3」をコピペして下さい。

とりあえず、[New Project]で(1ページ目)[Application name]:「KitchenTimer」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。

『<Project名>→app→main→res→drawable-hdpi』フォルダー(存在しない場合は作成してやる)内に「Android SDK」のデフォルト・アイコン「ic_menu_moreoverflow_normal_holo_light.png」をコピーする。
一応、「アプリの Logo マーク、通知用アイコン」を Android のマスコット・キャラの「Droid君」にしている。
通知用アイコンは白色が推奨されているらしいので、(一部 白ではないが)白を基調としている。
下の方に"drawable-hdpi"フォルダー用の画像ファイル(アプリの Logo マーク:「48x48」Pixel、通知用アイコン:「36x36」Pixel)をアップしてあります。
"drawable-mdpi"フォルダー用は手抜きして作ってないが、解像度が低い端末でも自動的に その端末の解像度に合うように縮小して表示される。
その場合、当然 画質は悪くなるので画質を綺麗にしたい場合は手抜きせずにチャント"drawable-mdpi"フォルダー用の画像ファイル(「32x32」Pixel)を作成してやれば良い。
なお、Activity の Toolbar の UI はスタンダードな「ActionBarActivity」のような感じになっている。


『<Project名>→app→main→java→com.example.kitchentimer→MainActivity(MainActivity.java)』


package com.example.kitchentimer;

import android.os.Build;
import android.os.Bundle;
import android.app.Activity;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.content.Context;
// import android.widget.Toolbar;
import android.view.KeyEvent;
import android.content.res.Resources;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;

// import android.app.AlarmManager;
import java.util.Timer;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;

import java.text.SimpleDateFormat;
import java.util.Date;

import java.io.IOException;
import android.content.pm.PackageManager.NameNotFoundException;

import android.view.View;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

import android.widget.EditText;
import android.widget.TextView;

import java.util.HashMap;

public class MainActivity extends AppCompatActivity
        implements View.OnClickListener {
    static final String fsDebugCrest = " 026";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    MainActivity oMainApp;
    Context oBaseContext;
    Context oAppContext; // Application Context.
    Context oContext; // Activity Context.
    Menu oMainMenu;

    String vsPackageName;
    String vsVersionName;
    int iVersionCode;
    String vsToolbarTitle;

    static final SimpleDateFormat oDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");

    static final int[] WIDGETS =
            new int[]{R.id.wbtStart, R.id.wbtCancel};
    TextView wtvMessage;
    TextView wtvSound;
    EditText wetHour, wetMinute, wetSecond;

    static final String fsTimerTask_Audio = "TimerAudio";
    // ↑ Timer は"TPlayer"と言う名前で1つだけしか作らない。

    // HashMap<Integer, String> dm1oMenuItem = new HashMap<Integer, String>( );
    // int iSelectMenuItem;
    HashMap<Integer, String> dm1oMenuSound = new HashMap<Integer, String>();
    int iSelectMenuSound;
    int iSelectSound;
    int iSoundPlayerPoolId = -1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (DEBUG) Log.i(tag, "Activity.onCreate( );");
        setContentView(R.layout.activity_main);
        oMainApp = this;
// System 共通の Context:別の Application とやりとりするとき。
        oBaseContext = getBaseContext();
// Application 固有の Context(Application ごとに Context が変化する):Application 共通のモノが対象。
        oAppContext = getApplicationContext();
// Activity 固有の Context(Activity ごとに Context が変化する):Activity に依存モノが対象。
        oContext = this;

        vsPackageName = getPackageName();
        try {
            PackageInfo packageInfo = this.getPackageManager().getPackageInfo(
                    this.getPackageName(), PackageManager.GET_META_DATA);
            // 「<Project名>→app→build.gradle」ファイル内の
            // 「versionName、versionCode」の設定値を取得。
            vsVersionName = packageInfo.versionName;
            iVersionCode = packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            // 例外処理
            e.printStackTrace();
        }

        wtvMessage = (TextView) findViewById(R.id.wtvMessage);
        wtvMessage.append("fsDebugCrest=" + fsDebugCrest + ";\n" +
                "PackageName=" + vsPackageName + ";\n" +
                "VersionName=" + vsVersionName + ";\n" +
                "BuildNo=" + String.valueOf(iVersionCode) + ";\n" +
                "");

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar actionbar = getSupportActionBar();

        // actionbar.setIcon(R.drawable.ic_menu_info_details);
        actionbar.setLogo(R.drawable.x_logo_android022);
        // ↑ 実際は Logo マークはアプリ固有の Icon にすべきモノです。
        // アプリ固有の Logo マークと言われてもイメージが湧かない方は
        // ブラウザの Icon(Logo マーク)をイメージしてみて下さい。
        // 下記サイトに主要ブラウザの Icon(Logo マーク)が掲載されています。
        // http://freesoft-100.com/pasokon/browser.html
        // ちなみに Toolbar に表示する Icon サイズのスタンダードは
        // "drawable-mdpi"フォルダー用は「32x32」、
        // "drawable-hdpi"フォルダー用は「48x48」だと思われます。

        vsToolbarTitle = "  " + getString(R.string.app_name) +
                fsDebugCrest +
                " V" + vsVersionName + // Version.
                " R" + String.valueOf(iVersionCode); // Revision.
// ↑ Toolbar に Logo マークを表示させるとタイトルと Logo マークとの間に隙間がないので、
// タイトルの先頭に半角スペースを2つ入れている。
        actionbar.setTitle(vsToolbarTitle);

        dm1oMenuSound.put(new Integer(R.id.menu_main_settings_MerryGoRound),
                "真夜中のメリーゴーランド");
        dm1oMenuSound.put(new Integer(R.id.menu_main_settings_MerryGoRound_ToyPiano),
                "真夜中のメリーゴーランド\n(ToyPiano)");
        dm1oMenuSound.put(new Integer(R.id.menu_main_settings_MerryGoRound_Harp),
                "真夜中のメリーゴーランド\n(Harp)");
        dm1oMenuSound.put(new Integer(R.id.menu_main_settings_Natsu_No_Nokoriga_Piano),
                "夏の残り香\n(Piano)");
        dm1oMenuSound.put(new Integer(R.id.menu_main_settings_Atarashii_Michi),
                "新しい道");

        iSelectMenuSound = R.id.menu_main_settings_MerryGoRound;
        iSelectSound = R.raw.mayonaka_no_merrygoround;
        // ↑リソース用ファイルの場合、
        // ファイル名には「英小文字、数字、アンダースコア」しか使用できないようだ。
        if (DEBUG) Log.i(tag, "Activity.onCreate( ) : " +
                "dm1oMenuSound.get(new Integer(iSelectMenuSound))=" +
                dm1oMenuSound.get(new Integer(iSelectMenuSound)) + "; " +
                "");
        wtvSound = (TextView) findViewById(R.id.wtvSound);
        wtvSound.setText(dm1oMenuSound.get(new Integer(iSelectMenuSound)));

        wetHour = (EditText) findViewById(R.id.wetHour);
        wetMinute = (EditText) findViewById(R.id.wetMinute);
        wetSecond = (EditText) findViewById(R.id.wetSecond);

        for (int iWId : WIDGETS) {
            View wvw = findViewById(iWId);
            wvw.setOnClickListener(this);
        }
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        if (DEBUG) Log.i(tag, "Activity.onRestart( );");
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (DEBUG) Log.i(tag, "Activity.onStart( );");
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (DEBUG) Log.i(tag, "Activity.onResume( );");
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (DEBUG) Log.i(tag, "Activity.onPause( );");
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (DEBUG) Log.i(tag, "Activity.onStop( );");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (DEBUG) Log.i(tag, "Activity.onDestroy( );");
    }

    @Override
    public void onClick(View v) {
        // super.onClick(v);
        if (DEBUG) Log.i(tag, "Activity.onClick( );");
        Intent si;
        long delay = 0;
        String s, vsH, vsM, vsS;
        long hour, minute, second;
        boolean err = false; // true; //
        switch (v.getId()) {
            case R.id.wbtStart:
                if (DEBUG) Log.i(tag, "wbtStart");
                hour = 0;
                minute = 0;
                second = 0;
                try {
                    s = wetHour.getText().toString();
                    if (!s.equals(""))
                        hour = Long.parseLong(s);
                    s = wetMinute.getText().toString();
                    if (!s.equals(""))
                        minute = Long.parseLong(s);
                    s = wetSecond.getText().toString();
                    if (!s.equals(""))
                        second = Long.parseLong(s);
                } catch (NumberFormatException e) {
                    // e.printStackTrace( );
                    wtvMessage.setText("入力エラーです、数字以外の文字が入力されました。\n");
                    err = true; // false; //
                }
                if (DEBUG) Log.i(tag, "wbtStart : " +
                        "hour=" + hour + "; " +
                        "minute=" + minute + "; " +
                        "second=" + second + "; " +
                        "");
                if (!err) {
                    delay = (long) ((hour * 60 + minute) * 60 + second) * 1000; // ms
                    if (DEBUG) Log.i(tag, "wbtStart : " +
                            "delay=" + delay + "; " +
                            "");
                    if (0 < delay) {
                        CreateTimer(fsTimerTask_Audio, delay);
                    }
                }
                break;
            case R.id.wbtCancel:
                if (DEBUG) Log.i(tag, "wbtCancel");

                wetHour.setText("");
                wetMinute.setText("");
                wetSecond.setText("");
                try {
                    if (DEBUG) Log.i(tag, "wbtCancel : " +
                            "dm1oTimerTask.get(fsTimerTask_Player)=" + MyFSTimerTask.dm1oTimerTask.get(fsTimerTask_Audio) + "; " +
                            "");
                    MyFSTimerTask_Audio oFSTT_Audio = (MyFSTimerTask_Audio) MyFSTimerTask.dm1oTimerTask.get(fsTimerTask_Audio);
                    synchronized (oFSTT_Audio) {
                        if ( null!=oFSTT_Audio.oMediaPlayer ) {
                            if ( oFSTT_Audio.oMediaPlayer.isPlaying( ) ) {
                                oFSTT_Audio.oMediaPlayer.stop( );
                            }
                            oFSTT_Audio.oMediaPlayer.release(); // リソースの解放
                            // ↑リソースの解放は必須のようです。
                            oFSTT_Audio.oMediaPlayer = null;
                        }
                        MyFSTimerTask_Audio.TaskCancel(oAppContext, fsTimerTask_Audio);
                    }
                } catch (NullPointerException e) {
                    // e.printStackTrace( );
                    if (DEBUG) Log.i(tag, "MainActivity.CreateTimer( ):NullPointerException;");
                }
                break;
            default:
                if (DEBUG) Log.i(tag, "default");
                return;
        }
        return;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        // アクションバー内で使用する為のメニューアイテムを実体化
        MenuInflater inflater = getMenuInflater();
        // MenuInflater inflater = getSupportMenuInflater();
        inflater.inflate(R.menu.main, menu);
        // 「R.menu.main」の「menu.main」は 恐らく「res」からの「フォルダー名+ファイル名」
        oMainMenu = menu;
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        if (DEBUG) Log.i(tag, "Activity.onOptionsItemSelected( );");
        switch (item.getItemId()) {
            case R.id.menu_main_settings:
                if (DEBUG) Log.i(tag, "menu_main_settings");

                break;
            case R.id.menu_main_settings_MerryGoRound:
                if (DEBUG) Log.i(tag, "menu_main_settings_MerryGoRound");

                iSelectMenuSound = item.getItemId();
                iSelectSound = R.raw.mayonaka_no_merrygoround;
                wtvSound.setText(dm1oMenuSound.get(new Integer(iSelectMenuSound)));
                break;
            case R.id.menu_main_settings_MerryGoRound_ToyPiano:
                if (DEBUG) Log.i(tag, "menu_main_settings_MerryGoRound_ToyPiano");

                iSelectMenuSound = item.getItemId();
                iSelectSound = R.raw.mayonaka_no_merrygoround_toypiano;
                wtvSound.setText(dm1oMenuSound.get(new Integer(iSelectMenuSound)));
                break;
            case R.id.menu_main_settings_MerryGoRound_Harp:
                if (DEBUG) Log.i(tag, "menu_main_settings_MerryGoRound_Harp");

                iSelectMenuSound = item.getItemId();
                iSelectSound = R.raw.mayonaka_no_merrygoround_harp;
                wtvSound.setText(dm1oMenuSound.get(new Integer(iSelectMenuSound)));
                break;
            case R.id.menu_main_settings_Natsu_No_Nokoriga_Piano:
                if (DEBUG) Log.i(tag, "menu_main_settings_Natsu_No_Nokoriga_Piano");

                iSelectMenuSound = item.getItemId();
                iSelectSound = R.raw.natsu_no_nokoriga_piano;
                wtvSound.setText(dm1oMenuSound.get(new Integer(iSelectMenuSound)));
                break;
            case R.id.menu_main_settings_Atarashii_Michi:
                if (DEBUG) Log.i(tag, "menu_main_settings_Atarashii_Michi");

                iSelectMenuSound = item.getItemId();
                iSelectSound = R.raw.atarashii_michi;
                wtvSound.setText(dm1oMenuSound.get(new Integer(iSelectMenuSound)));
                break;
            default:
                if (DEBUG) Log.i(tag, "default");
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                return false; // true; //
        }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        super.dispatchKeyEvent(event);
        if (DEBUG) Log.i(tag, "Activity.dispatchKeyEvent( );");
        final int action = event.getAction();
        final int keyCode = event.getKeyCode();
        switch (keyCode) {
            case KeyEvent.KEYCODE_MENU:
                if (DEBUG) Log.i(tag, "KeyEvent.KEYCODE_MENU;");
                if (action == KeyEvent.ACTION_UP) {
                    if (DEBUG) Log.i(tag, "KeyEvent.ACTION_UP;");
                    if (oMainMenu != null) {
                        if (DEBUG) Log.i(tag, "oMainMenu.performIdentifierAction( );");
                        oMainMenu.performIdentifierAction(R.id.menu_main_settings, 0);
                    }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
                    return true; // false; //
                }
                break;

            default:
                if (DEBUG) Log.i(tag, "KeyEvent:Default");

                break;
        }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
        return false; // true; //
    }

    public void CreateTimer(String vsN, long delay) {
        if (DEBUG) Log.i(tag, "MainActivity.CreateTimer( );");

        try {
            synchronized (MyFSTimerTask.dm1oTimerTask.get(vsN)) {
                if (null != MyFSTimerTask.dm1oTimerTask.get(vsN)) {
                    // Timer の多重登録時のエラー処理を記述。
                    // return;
                }
            }
        } catch (NullPointerException e) {
            // e.printStackTrace( );
            if (DEBUG) Log.i(tag, "MainActivity.CreateTimer( ):NullPointerException;");
        }

        Timer timer = new Timer();
        timer.schedule(new MyFSTimerTask_Audio(getApplicationContext(), timer, vsN, iSelectSound) {
            @Override
            @SuppressWarnings("deprecation")
            public void run() {
                if (DEBUG) Log.i(tag, "MyFSTimerTask_Audio.run( ) : " +
                        "タイマーが実行されました。");
                synchronized(this){
                    String vsDateTime = oDateFormat.format(
                            new Date(System.currentTimeMillis()));
                    if (DEBUG) Log.i(tag, "TimerTask_Audio.run( ) : " +
                            "vsName=" + vsName + "; " +
                            "vsDateTime=" + vsDateTime + "; " +
                            "");
                    // AudioManagerを取得する
                    AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
                    // Alarm の最大音量を取得する
                    int iVolumeMax = am.getStreamMaxVolume(AudioManager.STREAM_ALARM);
                    // Alarm の音量を取得する
                    int iVolume = am.getStreamVolume(AudioManager.STREAM_ALARM);
                    float vfVolume = (float) iVolume / iVolumeMax;
                    if (DEBUG) Log.i(tag, "MyFSTimerTask_Audio.run( ) : " +
                            "vfVolume=" + vfVolume + "; " +
                            "");
                    float vfLeftVolume = vfVolume;
                    float vfRightVolume = vfVolume;
                    lSoundRunning = true; // false; //
                    try {
                        if (null!=oMediaPlayer) {
                            if( oMediaPlayer.isPlaying( ) ) {
                                oMediaPlayer.stop( );
                            }
                            oMediaPlayer.release(); // リソースの解放
                            // ↑リソースの解放は必須のようです。
                            oMediaPlayer = null;
                        }
                        Resources res = getResources();
                        String rneme = res.getResourceName(iSoundId);
                        String rtype = res.getResourceTypeName(iSoundId);
                        String rentry = res.getResourceEntryName(iSoundId);
                        String rfile = "android.resource://" + getPackageName( ) + "/" + iSoundId;
                        if (DEBUG) Log.i(tag, "MyFSTimerTask_Audio.run( ) : " +
                                "rneme=" + rneme + "; " +
                                "rtype=" + rtype + "; " +
                                "rentry=" + rentry + "; " +
                                "rfile=" + rfile + "; " +
                                "");
                        Uri uri = Uri.parse(rfile);
                        // oMediaPlayer = MediaPlayer.create(oAppContext, iSoundId);
// StreamType の設定で「AudioManager.STREAM_MUSIC」を指定すると
// 「MediaPlayer、Ringtone」で音が鳴らない端末があるようです。
// 「MediaPlayer#setAudioStreamType( )」は使わずにデフォルト設定で使う事により、
// 「STREAM_MUSIC」になるようです。
// ただし「Ringtone#setStreamType( )」を使わずにデフォルト設定で使った場合は、
// 恐らくデフォルト値は「STREAM_RING」になると思われます。
// 「音楽:STREAM_MUSIC」以外の「アラーム:STREAM_ALARM、通知:STREAM_NOTIFICATION、
// 「着信:STREAM_RING、システム:STREAM_SYSTEM、通話:STREAM_VOICE_CALL」なら設定 可能だと思われます。
// ただし「Android 4.0」以上は「通知:STREAM_NOTIFICATION、着信:STREAM_RING」のボリュームが連動、
// 「Android 5.0」以上は「通知:STREAM_NOTIFICATION、着信:STREAM_RING、システム:STREAM_SYSTEM」
// のボリュームが連動するようです。
                        oMediaPlayer = new MediaPlayer();
                        oMediaPlayer.setDataSource(oAppContext, uri);
// この STREAM の種類は「アラーム音:STREAM_ALARM、ダイヤル音:STREAM_DTMF、
// 通知音:STREAM_NOTIFICATION、着信音:STREAM_RING、システム音:STREAM_SYSTEM、通話:STREAM_VOICE_CALL 」。
// 「setAudioStreamType( )」に上記の STREAM を設定する事により、
// MediaPlayer は その Stream Type に関連付けられ、
// その STREAM に設定されているボリューム(音量)でサウンドを鳴らせる。
// それは その STREAM の音源を参照する訳ではなく、
// あくまで その Stream Type のボリューム(音量)を参照しているだけ。
                        oMediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); // Alarm 音として設定
                        oMediaPlayer.setOnCompletionListener(this);
                        oMediaPlayer.setLooping(false); // true;
                        oMediaPlayer.prepare();  // メディア再生の準備(同期)
                        oMediaPlayer.start();
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                    } catch (IllegalStateException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    } catch (NullPointerException e) {
                        e.printStackTrace( );
                    }

                    lTimerRunning = false; // true; //
                    // TaskCancel(null, vsName);
                }
            }
            @Override
            public synchronized void onCompletion(MediaPlayer mp)  {
                if (null!=oMediaPlayer) {
                    oMediaPlayer.release( ); // リソースの解放
                    // ↑リソースの解放は必須のようです。
                    oMediaPlayer = null;
                }
                lSoundRunning = false; // true; //
                TaskCancel(null, vsName);
            }
        }, delay);
    }

}



『<Project名>→app→main→java→com.example.kitchentimer→MyService(MyService.java)』


package com.example.kitchentimer;

import android.app.Service;
import android.os.IBinder;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.app.Notification;
import android.support.v7.app.NotificationCompat;
import android.util.Log;

// import android.app.AlarmManager;
import java.util.Timer;

import java.text.SimpleDateFormat;
import java.util.Date;

public class MyService extends Service {
    static final String fsDebugCrest = MainActivity.fsDebugCrest;
    static final String tag = MainActivity.tag; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = MainActivity.DEBUG;
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。

    static final int ONGOING_NOTIFICATION = 1;

    static Object oAnchor_TimerTask_Array;
    // ↑ただのアンカーなので、アクセスする場合は
    // MyTimerTask の方の dm1oTimer をアクセスして下さい。

    // 下記「timer、delay、interval」は あくまでもテンポラリー。
    Timer timer; // = new Timer( );
    long delay; // ms
    long interval; // ms

    @Override
    public void onCreate( ){
        super.onCreate( );
        if (DEBUG) Log.i(tag, "MyService.onCreate( );");
    }

    @Override
    public int onStartCommand(Intent si, int f, int sid) {
        super.onStartCommand(si, f, sid);
        if(DEBUG)Log.i(tag, "MyService.onStartCommand( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");

// 良く分からないが、次の PendingIntent は
// Activity の取得の場合は「getActivity(〜)」、
// Activity の取得 以外は「getService(〜)」になると思われる。
        SettingForeground(PendingIntent.getActivity(
                this, 0, new Intent(this, MainActivity.class),
                PendingIntent.FLAG_CANCEL_CURRENT));

        return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
    }

    @Override
    public IBinder onBind(Intent si) {
        // super.onBind(si);
        return null;
    }

    public void SettingForeground(PendingIntent pi) {
        if(DEBUG)Log.i(tag, "MyService.SettingForeground( );");
        Notification notif = new NotificationCompat.Builder(this)
                .setContentIntent(pi)
                .setAutoCancel(true)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("Content Text.")
                // .setContentInfo("Content Info.")
                // .setTicker("Ticker.")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.drawable.x_slogo_android031) // SmallIcon を設定しないと通知されない(例外も発生しない)
                .build( );
        startForeground(ONGOING_NOTIFICATION, notif);
    }

    @Override
    public void onDestroy( ){
        super.onDestroy( );
        if (DEBUG) Log.i(tag, "MyService.onDestroy( );");

        oAnchor_TimerTask_Array = null;
        stopForeground(true);
    }

}



『<Project名>→app→main→java→com.example.kitchentimer→MyFSTimerTask(MyFSTimerTask.java)』


package com.example.kitchentimer;

import java.util.Timer;
import java.util.TimerTask;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

import java.text.SimpleDateFormat;
import java.util.Date;

import java.util.HashMap;

public abstract class MyFSTimerTask extends TimerTask {
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = MainActivity.DEBUG;
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。

    static Context oAppContext; // Application Context.
    // volatile:最適化の抑制.
    public volatile String vsName = "";
    public volatile Timer oTimer;
    public volatile boolean lTimerRunning = false; // true; //
    public volatile static HashMap<String, MyFSTimerTask> dm1oTimerTask = new HashMap<String, MyFSTimerTask>( );

    // SimpleDateFormat oDateFormat = MainActivity.oDateFormat;

    public MyFSTimerTask(Context actx, Timer t, String vsN){
        super( );
        if (DEBUG) Log.i(tag, "MyTimerTask( );");

        synchronized(this) {
            lTimerRunning = true; // false; //
            vsName = vsN;
            oTimer = t;
            TaskRemove(vsName);
            dm1oTimerTask.put(vsName, this);

            oAppContext = actx;
            Intent si = new Intent(oAppContext, MyService.class);
            // si.putExtra("SubjectKey", "Foreground");
            oAppContext.startService(si);
            MyService.oAnchor_TimerTask_Array = dm1oTimerTask;
        }
        if(DEBUG)Log.i(tag, "MyTimerTask( ) : "+
                "vsName="+vsName+"; "+
                "dm1oTimerTask.size( )="+dm1oTimerTask.size( )+"; "+
                "");
    }

    public static synchronized void TaskCancel(Context actx, String vsN){
        if (DEBUG) Log.i(tag, "MyTimerTask.TaskCancel( );");

        TaskRemove(vsN);
        Check_ServiceCancel(actx);
    }

    private static synchronized void TaskRemove(String vsN){
        if (DEBUG) Log.i(tag, "MyTimerTask.TaskRemove( );");

        MyFSTimerTask timertask = dm1oTimerTask.get(vsN);
        if( null!=timertask ){
            timertask.oTimer.cancel( );
            dm1oTimerTask.remove(vsN);
        }
    }

    private static synchronized void Check_ServiceCancel(Context actx){
        if (DEBUG) Log.i(tag, "MyTimerTask.Check_ServiceCancel( );");

        if( 0==dm1oTimerTask.size( ) ){
            if( null!=actx ){
                oAppContext = actx;
            }
            Intent si = new Intent(oAppContext, MyService.class);
            oAppContext.stopService(si);
        }
    }

}



『<Project名>→app→main→java→com.example.kitchentimer→MyFSTimerTask_Audio(MyFSTimerTask_Audio.java)』


package com.example.kitchentimer;

import java.util.Timer;
import android.media.MediaPlayer;
import android.content.Context;

public abstract class MyFSTimerTask_Audio extends MyFSTimerTask
        implements MediaPlayer.OnCompletionListener {

    int iSoundId;
    // volatile:最適化の抑制.
    // public volatile int iPoolId = -1;
    public volatile boolean lSoundRunning = false; // true; //
    public volatile MediaPlayer oMediaPlayer;
    // public volatile SoundPool oSoundPlayer;

    public MyFSTimerTask_Audio(Context actx, Timer t, String vsN, int iSI){
        super(actx, t, vsN);

        // lSoundRunning = true; // false; //
        iSoundId = iSI;
    }
}



『<Project名>→app→main→res→drawable→border.xml』(ファイルを作成)
※「TextView」の枠として使っている。


<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ffffff" />
    <padding android:top="3dp" android:bottom="3dp"
        android:left="7dp" android:right="7dp" />
    <stroke android:width="1px" android:color="#000000" />
    <corners android:radius="5dp" />
</shape>



『<Project名>→app→main→res→layout→activity_main.xml』


<?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"
    tools:context=".MainActivity"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ffffff"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/actionBarSize"
        android:background="#9b7fff"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_margin = "7dp"
        android:background="@drawable/border"
        android:text="Sound"
        android:id="@+id/wtvSound"
        android:lines="2" />

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

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="時間"
            android:id="@+id/wtvHour"
            android:layout_weight="1"
            android:gravity="center_horizontal"
            android:textColor="@android:color/black" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="分"
            android:id="@+id/wtvMinute"
            android:layout_weight="1"
            android:gravity="center_horizontal"
            android:textColor="@android:color/black" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="秒"
            android:id="@+id/wtvSecond"
            android:layout_weight="1"
            android:gravity="center_horizontal"
            android:textColor="@android:color/black" />
    </LinearLayout>

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

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:inputType="numberDecimal"
            android:ems="10"
            android:id="@+id/wetHour"
            android:layout_weight="1"
            android:gravity="right" />

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:inputType="numberDecimal"
            android:ems="10"
            android:id="@+id/wetMinute"
            android:layout_weight="1"
            android:gravity="right" />

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:inputType="numberDecimal"
            android:ems="10"
            android:id="@+id/wetSecond"
            android:layout_weight="1"
            android:gravity="right" />
    </LinearLayout>

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

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="Start"
            android:id="@+id/wbtStart"
            android:layout_weight="1" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="Cancel"
            android:id="@+id/wbtCancel"
            android:layout_weight="1" />
    </LinearLayout>

    <TextView
        android:id="@+id/wtvMessage"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_margin = "7dp"
        android:background="@drawable/border"
         android:layout_weight="1" />

</LinearLayout>



『<Project名>→app→src→main→res→menu→main.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<menu
    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"
    tools:context=".MainActivity">

    <item android:id="@+id/menu_main_settings"
        android:title="Settings"
        android:icon="@drawable/ic_menu_moreoverflow_normal_holo_light"
        app:showAsAction="always">
        <!-- ↑ showAsAction:Toolbar に対する表示設定。 -->
        <!-- "always":常に表示、"never":常に表示しない、"ifRoom":表示する余裕があれば表示。 -->
        <menu>
            <item android:id="@+id/menu_main_settings_MerryGoRound"
                android:title="メリーゴーランド">
            </item>

            <item android:id="@+id/menu_main_settings_MerryGoRound_ToyPiano"
                android:title="メリーゴーランド(ToyPiano)">
            </item>

            <item android:id="@+id/menu_main_settings_MerryGoRound_Harp"
                android:title="メリーゴーランド(Harp)">
            </item>

            <item android:id="@+id/menu_main_settings_Natsu_No_Nokoriga_Piano"
                android:title="夏の残り香(Piano)">
            </item>

            <item android:id="@+id/menu_main_settings_Atarashii_Michi"
                android:title="新しい道">
            </item>
        </menu>
    </item>

</menu>



『<Project名>→app→src→main→res→values→styles.xml』


<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>



『<Project名>→app→main→AndroidManifest.xml』


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

    <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:launchMode="singleInstance" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="MyService" >
        </service>
    </application>

</manifest>


<Number>: [00000A2E]  <Date>: 2016/05/03 23:50:43
<Title>: RingKitchenTimer 041
<Name>: amanojaku@管理人



前述の死ににくい KitchenTimer でフォルダー内の音源ファイルを鳴らす。
RingtoneManager も Preference もウマく行かなかったので、結局 自作する。
Ringtone の音源ファイルを Radio Button Ggroup にし Dialog Box で表示する。
Radio Button をタップし選択すると、選択された Radio Button の Key が Storage に保存される。
MediaPlayer より Ringtone の方が簡単に使えるので、このようなアラーム音しか鳴らさない場合は Ringtone がオススメです。

[Ringtone]は「Alarm、Notification、Ringtone」の3種類の Ringtone Type が選択できます。
[Volume]も「Alarm、Notification、Ringtone」の3種類の Volume Type が選択できます。
その下の[EditText]フィールドをタップすると[Ringtone]で設定された Ringtone Type の音源ファイルが表示されます。
その Radio Button をタップする事で音源ファイルを選択できます。
Radio Button をロングタッチする事により、音源ファイルを試聴できます、変更せずに そのまま Dialog Box を閉じたい場合は端末の[戻る]キーをタップして下さい。
<Silent>だけしか表示されない場合はフォルダー内に音源ファイルが存在するかどうか確認して下さい。
標準フォルダーは「"media/audio/alarms"、"media/audio/notifications"、"media/audio/ringtones"」です、非推奨フォルダーは「"Alarms"、"Notifications"、"Ringtones"」です(フォルダーが存在しない場合は作成して下さい)。
もし 音源ファイルが何もない場合は、フリーの音源ファイルをダウンロードして下さい。

フリー音源は下記「げきばん!」様からダウンロードできます。
フリー作品を使用する場合の注意点。
"利用規約"に違反した場合は違法となり罰金などが請求される可能性がありますので、必ず そのサイトの"利用規約"を お読み下さい。
(通常は)著作権を放棄しているものではありません、(通常は)再配布は禁止されています。
(大抵は)商用として使用できません、(大抵は)改変が禁止されています。
(場合によっては)著作権表示が義務付けられている事があります。

げきばん! オリジナルサウンドトラック制作(音楽・効果音・声・MA)
http://soundjewel.symphie.jp/product/gekiban/free/

「げきばん!」様の"利用規約"。

> すべて無料でダウンロードできます
> 商用としてご使用できます
> ご申告は不要です
> JASRACなどへのご申告も不要です
> 著作権表示、作者名のクレジットは不要です
> 編集やエフェクトを加えるなど、改変してご使用できます
> 音楽ファイルを販売するなど、著作権を侵害する使用はできません

再配布については書かれていませんが、(再配布は禁止されているのが通常なので)再配布は禁止と考えた方が無難でしょう。

当方がダウンロードしたファイルは「真夜中のメリーゴーランド:Mayonaka_No_Merry-Go-Round.wav」(変換後のファイル:mayonaka_no_merrygoround.mp3)、「真夜中のメリーゴーランド(トイピアノ):Mayonaka_No_Merry-Go-Round_Toy_Piano.wav」(変換後のファイル:mayonaka_no_merrygoround_toypiano.mp3)、「真夜中のメリーゴーランド(ハープ):Mayonaka_No_Merry-Go-Round_Harp.wav」(変換後のファイル:mayonaka_no_merrygoround_harp.mp3)、「夏の残り香(ピアノ):Natsu_No_Nokoriga_Piano.wav」(変換後のファイル:natsu_no_nokoriga_piano.mp3)、「新しい道:Atarashii_Michi.wav」(変換後のファイル:atarashii_michi.mp3)です。
※「げきばん!」様のサイト内・検索で これらのファイルを検索したい場合は、上記の日本語名で検索して下さい。
※ 音源の変換は「XMedia Recode Version 3.2.6.1」(フリー・ソフト)を使用しています。
ファイルによっては なぜか鳴らない場合があります、確認はしていませんが(特に低いバージョンの端末などは)"m4a"より"mp3"の方が安定して鳴るかも知れません。

とりあえずチェック用として Alarm 用ディレクトリだけにコピペしてみて下さい。
『<SDカード>→media→audio→alarms』フォルダー(存在しない場合は作成してやる)内に上記の「mayonaka_no_merrygoround.mp3、mayonaka_no_merrygoround_toypiano.mp3、mayonaka_no_merrygoround_harp.mp3、natsu_no_nokoriga_piano.mp3、atarashii_michi.mp3」をコピペして下さい。

とりあえず、[New Project]で(1ページ目)[Application name]:「RingKitchenTimer」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。

このプログラムは interface を使っているが、 interface を編集した場合は[Build]→[Clean Project]や、[Run]→[Clean and Rerun]など Clean を実行しないと変更が反映されないようだ。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。


『<Project名>→app→main→res→drawable-hdpi』フォルダー(存在しない場合は作成してやる)内に「Android SDK」のデフォルト・アイコン「ic_menu_moreoverflow_normal_holo_light.png」をコピーする。
一応、「アプリの Logo マーク、通知用アイコン」を Android のマスコット・キャラの「Droid君」にしている。
通知用アイコンは白色が推奨されているらしいので、(一部 白ではないが)白を基調としている。
下の方に『<Project名>→app→main→res→drawable-hdpi』フォルダー用の画像ファイル「アプリの Logo マーク:「48x48」Pixel、通知用アイコン:「36x36」Pixel、その他"TextView"用アイコンをアップしてあります。
"drawable-mdpi"フォルダー用は手抜きして作ってないが、解像度が低い端末でも自動的に その端末の解像度に合うように縮小して表示される。
その場合、当然 画質は悪くなるので画質を綺麗にしたい場合は手抜きせずにチャント"drawable-mdpi"フォルダー用の画像ファイルを作成してやれば良い。
なお、Activity の Toolbar の UI はスタンダードな「ActionBarActivity」のような感じになっている。

このプログラムの Visual Design の仮想の端末は「720x1280」の解像度に設定してプログラミングしています。
設定されている解像度が違うと表示が崩れる場合があるかもしれないので、Visual Design の仮想の端末を「720x1280」の解像度の端末に設定して下さい。


《参考》

KitchenTimer
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+00000A23.&


『<Project名>→app→main→java→com.example.ringkitchentimer→CommonDefine(CommonDefine.java)』


package com.example.ringkitchentimer;

import java.text.SimpleDateFormat;

interface CommonDefine{
    String fsDebugCrest = " 041";
    String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    static final String fsAscTab = new String(Character.toChars(0x09));
    static final SimpleDateFormat oDateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss SSS");
}



『<Project名>→app→main→java→com.example.ringkitchentimer→MainActivity(MainActivity.java)』


package com.example.ringkitchentimer;

import android.content.DialogInterface;
import android.media.RingtoneManager;
import android.os.Bundle;
import android.os.Build;
import android.os.Environment;
import android.support.v7.view.ActionMode;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.KeyEvent;
import android.app.Activity;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
// import android.widget.Toolbar;
import android.content.Context;
import android.content.res.Resources;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.content.Intent;
import android.app.PendingIntent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;

import android.view.View;
import android.widget.EditText;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;
import android.app.Dialog;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.RadioGroup;
import android.widget.RadioButton;
import android.view.MotionEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Arrays;
import java.util.Comparator;

import java.io.File;
import java.io.IOException;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.Ringtone;
import android.net.Uri;

public class MainActivity extends AppCompatActivity implements CommonDefine,
        View.OnClickListener, DialogInterface.OnDismissListener {

    MainActivity oMainApp;
    Context oBaseContext;
    Context oAppContext; // Application Context.
    Context oContext; // Activity Context.
    Menu oMainMenu;
    String vsToolbarTitle;

    DisplayMetrics oDisplayMetrics;
    public static int iPhysicalWidth, iPhysicalHeight;
    float vfDisplayMetricsDensity;
    String vsSDCardRoot;
    InputMethodManager oInputMethodManager;

    String vsPackageName;
    String vsVersionName;
    int iVersionCode;

    final int fiStorageRef_DataCompatibleVersion = 1;
    final String fsStorageRef_File = "Serialize.dat";
    static final String fsStorageRef_Ringtone_GLKey = "StorageRef_Ringtone_GLKey";
    static final String fsStorageRef_Volume_GLKey   = "StorageRef_Volume_GLKey";
    static final String fsStorageRef_Sound_GLKey    = "StorageRef_Sound_GLKey";
    // String vsStorageRef_Ringtone_File;
    String vsRingtone_Name;
    String vsTimer_Ringtone_Name;
    int iTimer_Ringtone_VolumeType;

    static final int[] WIDGETS =
            new int[]{ R.id.wtvbRingtone, R.id.wtvbVolume, R.id.wetSound,
                    R.id.wbtStart, R.id.wbtCancel };

    EditText wetMessage;
    EditText wetSound;
    EditText wetHour, wetMinute, wetSecond;

    static final String fsTimerTask_Sound = "TimerSound";
    // ↑ Timer は"TimerSound"と言う名前で1つだけしか作らない。

    // HashMap<Integer, String> dm1oMenuItem = new HashMap<Integer, String>( );
    // int iSelectMenuItem;
    HashMap<Integer, String> dm1oMenuSound = new HashMap<Integer, String>();
    int iSelectMenuSound;
    int iSelectSound;

    Dialog oDRingtone, oDVolume, oDSound;
    RadioGroupDriver_StorageReference oGRRingtone, oGRVolume;
    RadioGroupDriver_StorageRef_Ringtone oGRSound;

    static final String fsStreamType_Alarm_Key = "Alarm_Key";
    static final String fsStreamType_Notification_Key = "Notification_Key";
    static final String fsStreamType_Ringtone_Key = "Ringtone_Key";
    HashMap<String, Integer> dm1oStreamKeyType = new HashMap<String, Integer>( );
    String[] d1vsStreamType;

    // DialogInterface.OnDismissListener DRingtoneDismissCallback;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (DEBUG) Log.i(tag, "Activity.onCreate( );");
        setContentView(R.layout.activity_main);
        oMainApp = this;
// System 共通の Context:別の Application とやりとりするとき。
        oBaseContext = getBaseContext();
// Application 固有の Context(Application ごとに Context が変化する):Application 共通のモノが対象。
        oAppContext = getApplicationContext();
// Activity 固有の Context(Activity ごとに Context が変化する):Activity に依存モノが対象。
        oContext = this;
        oDisplayMetrics = oAppContext.getResources().getDisplayMetrics();
        vfDisplayMetricsDensity = oDisplayMetrics.density;
        iPhysicalWidth = oDisplayMetrics.widthPixels;
        iPhysicalHeight = oDisplayMetrics.heightPixels;
        vsSDCardRoot = Environment.getExternalStorageDirectory( ).getPath( );
        if (DEBUG) Log.i(tag, "Activity.onCreate( ) : " +
                "vfDisplayMetricsDensity=" + vfDisplayMetricsDensity + "; " +
                "DisplayMetrics.widthPixels=" + iPhysicalWidth + "; " +
                "DisplayMetrics.heightPixels=" + iPhysicalHeight + "; " +
                "vsSDCardRoot" + vsSDCardRoot + "; " +
                "");

        oInputMethodManager = (InputMethodManager) oContext
                .getSystemService(Context.INPUT_METHOD_SERVICE);

        vsPackageName = getPackageName();
        try {
            PackageInfo packageInfo = this.getPackageManager().getPackageInfo(
                    this.getPackageName(), PackageManager.GET_META_DATA);
            // 「<Project名>→app→build.gradle」ファイル内の
            // 「versionName、versionCode」の設定値を取得。
            vsVersionName = packageInfo.versionName;
            iVersionCode = packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            // 例外処理
            e.printStackTrace();
        }

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar actionbar = getSupportActionBar();

        // actionbar.setIcon(R.drawable.ic_menu_info_details);
        actionbar.setLogo(R.drawable.x_logo_android022);
        // ↑ 実際は Logo マークはアプリ固有の Icon にすべきモノです。
        // アプリ固有の Logo マークと言われてもイメージが湧かない方は
        // ブラウザの Icon(Logo マーク)をイメージしてみて下さい。
        // 下記サイトに主要ブラウザの Icon(Logo マーク)が掲載されています。
        // http://freesoft-100.com/pasokon/browser.html
        // ちなみに Toolbar に表示する Icon サイズのスタンダードは
        // "drawable-mdpi"フォルダー用は「32x32」、
        // "drawable-hdpi"フォルダー用は「48x48」だと思われます。

        vsToolbarTitle = "  " + getString(R.string.app_name) +
                fsDebugCrest +
                " V" + vsVersionName + // Version.
                " R" + String.valueOf(iVersionCode); // Revision.
// ↑ Toolbar に Logo マークを表示させるとタイトルと Logo マークとの間に隙間がないので、
// タイトルの先頭に半角スペースを2つ入れている。
        actionbar.setTitle(vsToolbarTitle);

        wetMessage = (EditText) findViewById(R.id.wetMessage);
// ↑「longClickable="false"」に設定しているのでロングタッチ・イベントは取得できない。
// ロングタッチ・イベントを取得したい場合は、「longClickable="false"」を削除して下さい。
        oInputMethodManager.hideSoftInputFromWindow(wetMessage.getWindowToken( ), 0);
// ↑ IME を非表示に設定。
        wetMessage.append("fsDebugCrest=" + fsDebugCrest + ";\n" +
                "PackageName=" + vsPackageName + ";\n" +
                "VersionName=" + vsVersionName + ";\n" +
                "BuildNo=" + String.valueOf(iVersionCode) + ";\n" +
                "vfDisplayMetricsDensity=" + vfDisplayMetricsDensity + ";\n" +
                "vsSDCardRoot=" + vsSDCardRoot + ";\n" +
                "");

        wetSound = (EditText) findViewById(R.id.wetSound);
// ↑「longClickable="false"」に設定しているのでロングタッチ・イベントは取得できない。
// ロングタッチ・イベントを取得したい場合は、「longClickable="false"」を削除して下さい。
        oInputMethodManager.hideSoftInputFromWindow(wetSound.getWindowToken( ), 0);
// ↑ IME を非表示に設定。
        // wetSound.setText( );

        wetHour = (EditText) findViewById(R.id.wetHour);
        wetMinute = (EditText) findViewById(R.id.wetMinute);
        wetSecond = (EditText) findViewById(R.id.wetSecond);

        dm1oStreamKeyType.put(
                fsStreamType_Alarm_Key, new Integer(AudioManager.STREAM_ALARM));
        dm1oStreamKeyType.put(
                fsStreamType_Notification_Key, new Integer(AudioManager.STREAM_NOTIFICATION));
        dm1oStreamKeyType.put(
                fsStreamType_Ringtone_Key, new Integer(AudioManager.STREAM_RING));
        d1vsStreamType = new String[]{
                fsStreamType_Alarm_Key, "Alarm",
                fsStreamType_Notification_Key, "Notification",
                fsStreamType_Ringtone_Key, "Ringtone",
        };

        for (int wid : WIDGETS) {
            View wvw = findViewById(wid);
            wvw.setOnClickListener(this);
        }

    }

    @Override
    protected void onResume() {
        super.onResume();
        if (DEBUG) Log.i(tag, "Activity.onResume( );");
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (DEBUG) Log.i(tag, "Activity.onStart( );");

        TextView master;
        boolean selector, longtouch, silent;
        int iRingtoneType;
        Integer oiRingtoneType;
        String[] d1vsFExtension;
        DialogInterface.OnDismissListener dismiss;

// 端末のホーム・ボタンが押されると、Activity の「onStop( )」メソッドが呼ばれるが、
// そこで「oGRRingtone、oGRVolume、oGRSound」などのリソースの開放をしているので、
// 下記の「oGRRingtone、oGRVolume、oGRSound」などの記述は
// 「onCreate( )」メソッド内ではなく、「onStart( )」メソッド内で記述しなければならない。

// 必ず「onStop( )」メソッドで下記のように資源を Release して下さい。
// 資源を Release しないとメモリー・リークするかもしれません。
        // if( null!=oGRRingtone ) oGRRingtone.Release( );
        // if( null!=oGRVolume ) oGRVolume.Release( );
        // if( null!=oGRSound ) oGRSound.Release( );

        master = (TextView)findViewById(R.id.wtvbRingtone);
        selector = true; // false; // ← true にすると RadioButton 選択時に すぐに Dialog を閉じる。
        longtouch = false; // true; // ← false にすると Longtouch で RadioButton を ON にしない。
        dismiss = null;
// この oGRRingtone は Activity 内の表示コンポーネントではないので、
// この oGRRingtone は Activity 内の Layout などに登録する必要はない。
// 又、この oGRRingtone はイベントによって起動するが、
// イベント終了後も生存するのでローカル変数ではならない。
        oGRRingtone = new RadioGroupDriver_StorageReference(
                oContext, master, fiStorageRef_DataCompatibleVersion,
                fsStorageRef_File, fsStorageRef_Ringtone_GLKey,
                selector, longtouch,
                android.R.style.TextAppearance_Large, dismiss);
        oGRRingtone.addCreateRadioArray( d1vsStreamType );
        // oGRRingtone.Load( );
        oGRRingtone.setDefaultChecked(true); // false; //
        oDRingtone = oGRRingtone.getCreateRadioDialog("Ringtone");
        oGRRingtone.Save( );

        master = (TextView)findViewById(R.id.wtvbVolume);
        selector = true; //  true; // ← true にすると RadioButton 選択時に すぐに Dialog を閉じる。
        longtouch = false; // true; // ← false にすると Longtouch で RadioButton を ON にしない。
        dismiss = null;
// この oGRVolume は Activity 内の表示コンポーネントではないので、
// この oGRVolume は Activity 内の Layout などに登録する必要はない。
// 又、この oGRVolume はイベントによって起動するが、
// イベント終了後も生存するのでローカル変数ではならない。
        oGRVolume = new RadioGroupDriver_StorageReference(
                oContext, master, fiStorageRef_DataCompatibleVersion,
                fsStorageRef_File, fsStorageRef_Volume_GLKey,
                selector, longtouch,
                android.R.style.TextAppearance_Large, dismiss);
        oGRVolume.addCreateRadioArray( d1vsStreamType );
        // oGRVolume.Load( );
        oGRVolume.setDefaultChecked(true); // false; //
        oDVolume = oGRVolume.getCreateRadioDialog("Volume");
        oGRVolume.Save( );

// 下記は「R.id.wetSound」に対する初期設定が目的。
// 「oGRRingtone」のデータを参照しているので「oGRRingtone」の設定後に記述しなければならない。
        oiRingtoneType = dm1oStreamKeyType.get(oGRRingtone.getCheckedKey( ));
        iRingtoneType = -1;
        if( null!=oiRingtoneType ){
            iRingtoneType = oiRingtoneType.intValue( );
        }
        master = (TextView)findViewById(R.id.wetSound);
        silent = true; // false; // ← true にすると Silent 項目の RadioButton が先頭に表示されます。
        d1vsFExtension =  new String[]{ ".mp3", ".m4a", }; // ".asf", ".wma",
        // ↑有効化するファイル拡張子を指定。
        selector = true; //  false; // ← true にすると RadioButton 設定時に すぐに Dialog を閉じる。
        longtouch = false; // true; // ← false にすると Longtouch で RadioButton を ON にしない。
        dismiss = this;
// この oGRSound は Activity 内の表示コンポーネントではないので、
// この oGRSound は Activity 内の Layout などに登録する必要はない。
// 又、この oGRSound はイベントによって起動するが、
// イベント終了後も しばらくの間は生存するのでローカル変数ではならない。
        oGRSound = new RadioGroupDriver_StorageRef_Ringtone(
                oContext, master, fiStorageRef_DataCompatibleVersion,
                fsStorageRef_File, fsStorageRef_Sound_GLKey,
                selector, longtouch,
                android.R.style.TextAppearance_Large,
                silent, iRingtoneType, d1vsFExtension, dismiss);
        // oGRSound.Load( );
        oGRSound.setDefaultChecked(true); // false; //
        oDSound = oGRSound.getCreateRadioDialog("Sound");
        oGRSound.Save( );

        vsRingtone_Name = oGRSound.getCheckedKey( );

        if(DEBUG)Log.i(tag, "Activity.onStart( ) : "+
                "vsRingtone_Name="+vsRingtone_Name+"; "+
                "");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        if (DEBUG) Log.i(tag, "Activity.onRestart( );");
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (DEBUG) Log.i(tag, "Activity.onPause( );");
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (DEBUG) Log.i(tag, "Activity.onStop( );");

        if( null!=oGRRingtone ) oGRRingtone.Release( );
        if( null!=oGRVolume ) oGRVolume.Release( );
        if( null!=oGRSound ) oGRSound.Release( );
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (DEBUG) Log.i(tag, "Activity.onDestroy( );");
    }

    @Override
    public void onClick(View v) {
        // super.onClick(v);
        if (DEBUG) Log.i(tag, "Activity.onClick( );");
        Intent si;
        long delay = 0;
        String s, vsH, vsM, vsS;
        long hour, minute, second;
        boolean err = false; // true; //
        switch (v.getId()) {
            case R.id.wtvbRingtone :
                if (DEBUG) Log.i(tag, "wbtRingtone");

                oDRingtone.show( );

                break;
            case R.id.wtvbVolume :
                if (DEBUG) Log.i(tag, "wbtRingtone");

                oDVolume.show( );

                break;
            case R.id.wetSound :
                if (DEBUG) Log.i(tag, "wetSound");

// 必ず「onStop( )」メソッドで下記のように資源を Release して下さい。
// 資源を Release しないとメモリー・リークするかもしれません。
                // if( null!=oGRSound ) oGRSound.Release( );

                TextView master;
                boolean selector, longtouch, silent;
                int iRingtoneType;
                Integer oiRingtoneType;
                String[] d1vsFExtension;
                DialogInterface.OnDismissListener dismiss;

                oiRingtoneType = dm1oStreamKeyType.get(oGRRingtone.getCheckedKey( ));
                iRingtoneType = -1;
                if( null!=oiRingtoneType ){
                    iRingtoneType = oiRingtoneType.intValue( );
                }
                master = (TextView)findViewById(R.id.wetSound);
                selector = true; //  false; // ← true にすると RadioButton 設定時に すぐに Dialog を閉じる。
                longtouch = false; // true; // ← false にすると Longtouch で RadioButton を ON にしない。
                silent = true; // false; // ← true にすると Silent 項目の RadioButton が先頭に表示されます。
                d1vsFExtension =  new String[]{ ".mp3", ".m4a", }; // ".asf", ".wma",
                // ↑有効化するファイル拡張子を指定。
                dismiss = this;
// この oGRSound は Activity 内の表示コンポーネントではないので、
// この oGRSound は Activity 内の Layout などに登録する必要はない。
// 又、この oGRSound はイベントによって起動するが、
// イベント終了後も しばらくの間は生存するのでローカル変数ではならない。
                oGRSound = new RadioGroupDriver_StorageRef_Ringtone(
                        oContext, master, fiStorageRef_DataCompatibleVersion,
                        fsStorageRef_File, fsStorageRef_Sound_GLKey,
                        selector, longtouch,
                        android.R.style.TextAppearance_Large,
                        silent, iRingtoneType, d1vsFExtension, dismiss);
                // oGRSound.Load( );
                oGRSound.setDefaultChecked(true); // false; //
                oDSound = oGRSound.getCreateRadioDialog("Sound");
                oGRSound.Save( );

                oDSound.show( );

                break;
            case R.id.wbtStart:
                if (DEBUG) Log.i(tag, "wbtStart");
                hour = 0;
                minute = 0;
                second = 0;
                try {
                    s = wetHour.getText().toString();
                    if (!s.equals(""))
                        hour = Long.parseLong(s);
                    s = wetMinute.getText().toString();
                    if (!s.equals(""))
                        minute = Long.parseLong(s);
                    s = wetSecond.getText().toString();
                    if (!s.equals(""))
                        second = Long.parseLong(s);
                } catch (NumberFormatException e) {
                    // e.printStackTrace( );
                    wetMessage.setText("入力エラーです、数字以外の文字が入力されました。\n");
                    err = true; // false; //
                }
                if (DEBUG) Log.i(tag, "wbtStart : " +
                        "hour=" + hour + "; " +
                        "minute=" + minute + "; " +
                        "second=" + second + "; " +
                        "");
                if (!err) {
                    delay = (long) ((hour * 60 + minute) * 60 + second) * 1000; // ms
                    if (DEBUG) Log.i(tag, "wbtStart : " +
                            "delay=" + delay + "; " +
                            "");
                    if (0 < delay) {
                        CreateTimer(fsTimerTask_Sound, delay);
                    }
                }
                break;
            case R.id.wbtCancel:
                if (DEBUG) Log.i(tag, "wbtCancel");

                wetHour.setText("");
                wetMinute.setText("");
                wetSecond.setText("");
                try {
                    if (DEBUG) Log.i(tag, "wbtCancel : " +
                            "dm1oTimerTask.get(fsTimerTask_Player)=" + MyFSTimerTask.dm1oTimerTask.get(fsTimerTask_Sound) + "; " +
                            "");
                    MyFSTimerTask_Sound oFSTT_Sound = (MyFSTimerTask_Sound) MyFSTimerTask.dm1oTimerTask.get(fsTimerTask_Sound);
                    synchronized (oFSTT_Sound) {
                        if( null!=oFSTT_Sound && null!=oFSTT_Sound.oRingtone ) {
                            if(null!=oFSTT_Sound.oRingtone) {
                                if( oFSTT_Sound.oRingtone.isPlaying( ) ){
                                    oFSTT_Sound.oRingtone.stop();
                                }
                                oFSTT_Sound.oRingtone = null;
                            }
                        }
                        MyFSTimerTask_Sound.TaskCancel(oAppContext, fsTimerTask_Sound);
                    }
                } catch (NullPointerException e) {
                    // e.printStackTrace( );
                    if (DEBUG) Log.i(tag, "MainActivity.CreateTimer( ):NullPointerException;");
                }
                break;
            default:
                if (DEBUG) Log.i(tag, "default");
                return;
        }
        return;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        // アクションバー内で使用する為のメニューアイテムを実体化
        MenuInflater inflater = getMenuInflater();
        // MenuInflater inflater = getSupportMenuInflater();
        inflater.inflate(R.menu.main, menu);
        // 「R.menu.main」の「menu.main」は 恐らく「res」からの「フォルダー名+ファイル名」
        oMainMenu = menu;
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        if (DEBUG) Log.i(tag, "Activity.onOptionsItemSelected( );");
        switch (item.getItemId()) {
            case R.id.menu_main_settings:
                if (DEBUG) Log.i(tag, "menu_main_settings");

                break;
            case R.id.menu_main_settings_item1 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item1");

                break;
            case R.id.menu_main_settings_item2 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item2");

                break;
            case R.id.menu_main_settings_item3 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item3");

                break;
            default:
                if (DEBUG) Log.i(tag, "default");
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                return false; // true; //
        }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        super.dispatchKeyEvent(event);
        if (DEBUG) Log.i(tag, "Activity.dispatchKeyEvent( );");
        final int action = event.getAction();
        final int keyCode = event.getKeyCode();
        switch (keyCode) {
            case KeyEvent.KEYCODE_MENU:
                if (DEBUG) Log.i(tag, "KeyEvent.KEYCODE_MENU;");
                if (action == KeyEvent.ACTION_UP) {
                    if (DEBUG) Log.i(tag, "KeyEvent.ACTION_UP;");
                    if (oMainMenu != null) {
                        if (DEBUG) Log.i(tag, "oMainMenu.performIdentifierAction( );");
                        oMainMenu.performIdentifierAction(R.id.menu_main_settings, 0);
                    }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
                    return true; // false; //
                }
                break;

            default:
                if (DEBUG) Log.i(tag, "KeyEvent:Default");

                break;
        }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
        return false; // true; //
    }

    @Override
    public synchronized void onDismiss(DialogInterface dialog){
        if (DEBUG) Log.i(tag, "MainActivity.onDismiss( );");
        if(oDSound==dialog){
            if (DEBUG) Log.i(tag, "if(oDSound==dialog)");
            vsRingtone_Name = oGRSound.getCheckedKey( );
            if(DEBUG)Log.i(tag, "MainActivity.onDismiss( ) : "+
                    "vsRingtone_FKey="+vsRingtone_Name+"; "+
                    "");
        }
    }

    public void CreateTimer(String vsN, long delay) {
        if (DEBUG) Log.i(tag, "MainActivity.CreateTimer( );");

        try {
            synchronized (MyFSTimerTask.dm1oTimerTask.get(vsN)) {
                if (null != MyFSTimerTask.dm1oTimerTask.get(vsN)) {
                    // Timer の多重登録時のエラー処理を記述。
                    // return;
                }
            }
        } catch (NullPointerException e) {
            // e.printStackTrace( );
            if (DEBUG) Log.i(tag, "MainActivity.CreateTimer( ):NullPointerException;");
        }

        int iVolumeType;
        Integer oiVolumeType;
        oiVolumeType = dm1oStreamKeyType.get(oGRVolume.getCheckedKey( ));
        iVolumeType = -1;
        if( null!=oiVolumeType ){
            iVolumeType = oiVolumeType.intValue( );
        }
        iTimer_Ringtone_VolumeType = iVolumeType;
        vsTimer_Ringtone_Name = vsRingtone_Name;

        Timer timer = new Timer();
        timer.schedule(new MyFSTimerTask_Sound(getApplicationContext(), timer, vsN, iSelectSound) {
            @Override
            @SuppressWarnings("deprecation")
            public void run() {
                if (DEBUG) Log.i(tag, "MyFSTimerTask_Sound.run( ) : " +
                        "タイマーが実行されました。");
                synchronized(this){
                    String vsDateTime = oDateFormat.format(
                            new Date(System.currentTimeMillis()));
                    if (DEBUG) Log.i(tag, "TimerTask_Sound.run( ) : " +
                            "vsName=" + vsName + "; " +
                            "vsDateTime=" + vsDateTime + "; " +
                            "");
                    // AudioManagerを取得する
                    AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
                    // Alarm の最大音量を取得する
                    int iVolumeMax = am.getStreamMaxVolume(AudioManager.STREAM_ALARM);
                    // Alarm の音量を取得する
                    int iVolume = am.getStreamVolume(AudioManager.STREAM_ALARM);
                    float vfVolume = (float) iVolume / iVolumeMax;
                    if (DEBUG) Log.i(tag, "MyFSTimerTask_Sound.run( ) : " +
                            "vfVolume=" + vfVolume + "; " +
                            "");
                    float vfLeftVolume = vfVolume;
                    float vfRightVolume = vfVolume;
                    lSoundRunning = true; // false; //
                    try {
                        if( null!=oRingtone ) {
                            if( oRingtone.isPlaying( ) ){
                                oRingtone.stop();
                            }
                            oRingtone = null;
                        }
                        Uri uri = Uri.parse(vsTimer_Ringtone_Name);
// StreamType の設定で「AudioManager.STREAM_MUSIC」を指定すると
// 「MediaPlayer、Ringtone」で音が鳴らない端末があるようです。
// 「MediaPlayer#setAudioStreamType( )」は使わずにデフォルト設定で使う事により、
// 「STREAM_MUSIC」になるようです。
// ただし「Ringtone#setStreamType( )」を使わずにデフォルト設定で使った場合は、
// 恐らくデフォルト値は「STREAM_RING」になると思われます。
// 「音楽:STREAM_MUSIC」以外の「アラーム:STREAM_ALARM、通知:STREAM_NOTIFICATION、
// 「着信:STREAM_RING、システム:STREAM_SYSTEM、通話:STREAM_VOICE_CALL」なら設定 可能だと思われます。
// ただし「Android 4.0」以上は「通知:STREAM_NOTIFICATION、着信:STREAM_RING」のボリュームが連動、
// 「Android 5.0」以上は「通知:STREAM_NOTIFICATION、着信:STREAM_RING、システム:STREAM_SYSTEM」
// のボリュームが連動するようです。
                        oRingtone = RingtoneManager.getRingtone(oAppContext, uri);
                        oRingtone.setStreamType(iTimer_Ringtone_VolumeType);
                        oRingtone.play( );
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                    } catch (IllegalStateException e) {
                        e.printStackTrace();
                    } catch (NullPointerException e) {
                        e.printStackTrace();
                    }
                    lTimerRunning = false; // true; //
                }
            }
        }, delay);
    }

    public class RadioGroupDriver extends RadioGroup implements
            RadioGroup.OnCheckedChangeListener,
            View.OnLongClickListener, View.OnTouchListener,
            DialogInterface.OnDismissListener {

        int iRadioTextColor;
        int iTextAppearance;
        final String fsRadioName_Key = "RadioName_Key";
        volatile TextView wtvMaster;
        public volatile Dialog wdgBody;
        DialogInterface.OnDismissListener oDismissCallback;
        volatile boolean lOnLongClick = false; // true; //
        volatile boolean lSelectorMode = false; // true; //
        volatile boolean lLongtouchOn = false; // true; //
// ↑ lSelectorMode が true の場合、選択時に Dialog を すぐに閉じる。

        // volatile:最適化の抑制.
        volatile HashMap<String, RadioButton> dm1oKeyRadio = new HashMap<String, RadioButton>();
        volatile HashMap<RadioButton, String> dm1oRadioKey = new HashMap<RadioButton, String>();
        volatile String vsCheckedKey = null;
        public volatile ArrayList<String> dl1sIndexKey = new ArrayList<String>( );

        public RadioGroupDriver(
                Context ctx, TextView master, boolean selector, boolean longtouch,
                int appearance, DialogInterface.OnDismissListener dismiss) {
            super(ctx);
            synchronized(this) {
                wtvMaster = master;
                iTextAppearance = appearance;
                lSelectorMode = selector;
                lLongtouchOn = longtouch;
                oDismissCallback = dismiss;
                iRadioTextColor = ContextCompat.getColor(getContext( ), R.color.holo_blue_light);
// ↑端末によって Dialog の Background が白系だったり、黒系だったりするので
// どちらでも見やすい TextColor にしてやる。
                setOnCheckedChangeListener(this);
            }
        }

        public synchronized void Release( ) {
            wtvMaster = null;
            wdgBody = null;
        }

        public synchronized String getCheckedKey( ) {
            return vsCheckedKey;
        }

        void setCheckedKey(String rckey) {
            vsCheckedKey = rckey;
        }

        @SuppressWarnings("deprecation")
        public synchronized RadioButton addCreateRadio(String key, String title, boolean check) {
            if (DEBUG) Log.i(tag, "RadioGroupDriver.addCreateRadio( );");
            RadioButton radio;
            /*
            if(DEBUG)Log.i(tag, "RadioGroupDriver.addCreateRadio( ) : "+
                    "title="+title+"; "+
                    "key="+key+"; "+
                    "");
            */
            if (null != dm1oKeyRadio.get(key)) {
                throw new IllegalArgumentException("二重登録:同じキーで二重に登録しようとしました。");
            } else {
                radio = new RadioButton(getContext( ));
                super.addView(radio);
                dl1sIndexKey.add(key);
                dm1oKeyRadio.put(key, radio);
                dm1oRadioKey.put(radio, key);
                radio.setText(title);
                radio.setTextAppearance(getContext( ), iTextAppearance);
                radio.setTextColor(iRadioTextColor);
                // radio.setChecked(check); // true; // false;
                if (check) {
                    setRadioChecked(radio);
                }
                radio.setOnLongClickListener(this);
                radio.setOnTouchListener(this);
            }
            return radio;
        }

        public synchronized RadioButton addCreateRadio(String key, String title) {
            return addCreateRadio(key, title, false); // true; // ;
        }

        public synchronized void addCreateRadioArray(String[] radios) {
            String key, title;
            int i = 0;
            while(i < radios.length ) {
                key = radios[i];
                i++;
                title = radios[i];
                i++;
                addCreateRadio(key, title, false); // true; // ;
            }
        }

        public synchronized void setDefaultChecked( ) {
            if (DEBUG) Log.i(tag, "RadioGroupDriver.setDefaultChecked( );");
            Ringtone ringtone = null;
            String rckey = null;
            RadioButton radio = null;
            if(DEBUG)Log.i(tag, "RadioGroupDriver.setDefaultChecked( ) : "+
                    "dl1sIndexKey.get(0)="+dl1sIndexKey.get(0)+"; "+
                    "getCheckedKey( )="+getCheckedKey( )+"; "+
                    "");
            rckey = getCheckedKey( );
            if( null==rckey && 0<dl1sIndexKey.size( ) ){
                rckey = dl1sIndexKey.get(0);
            }
            if( 0<dl1sIndexKey.size( ) ) {
                // key = dl1sIndexKey.get(0);
                setRadioCheckedKey(rckey);
                MasterReflection( );
                if(DEBUG)Log.i(tag, "RadioGroupDriver.setDefaultChecked( ) : "+
                        "rckey ="+rckey+"; "+
                        "getCheckedKey( )="+getCheckedKey( )+"; "+
                        "");
            }
        }

        public synchronized void setRadioChecked(RadioButton radio) {
            if (DEBUG) Log.i(tag, "RadioGroupDriver.setRadioChecked( );");
            setCheckedKey(null);
            if (null != radio) {
                String rckey = dm1oRadioKey.get(radio);
                setCheckedKey(rckey);
                if(DEBUG)Log.i(tag, "RadioGroupDriver.setRadioChecked( ) : "+
                        "getCheckedKey( )="+getCheckedKey( )+"; "+
                        "");
                if (null != rckey) {
                    radio.setChecked(true); // false; //
                    for (int i = 0; i < this.getChildCount(); i++) {
                        RadioButton rb = (RadioButton) this.getChildAt(i);
                        if (rb.getId() != radio.getId()) {
                            rb.setChecked(false); // true; //
                        }
                    }
                }
            }
        }

        public synchronized void setRadioCheckedKey(String rckey) {
            RadioButton radio = dm1oKeyRadio.get(rckey);
            setRadioChecked(radio);
        }

        public synchronized Dialog getCreateRadioDialog(String title) {
            ScrollView sv = new ScrollView(getContext( ));
            sv.addView(this, new ScrollView.LayoutParams(
                    ScrollView.LayoutParams.WRAP_CONTENT,
                    ScrollView.LayoutParams.WRAP_CONTENT));

            LinearLayout layout = new LinearLayout(getContext( ));
            layout.addView(sv, new LinearLayout.LayoutParams(
                    LinearLayout.LayoutParams.WRAP_CONTENT,
                    LinearLayout.LayoutParams.WRAP_CONTENT));
            float dmd = vfDisplayMetricsDensity;
            layout.setPadding((int) (5 * dmd), (int) (0 * dmd), (int) (10 * dmd), (int) (0 * dmd));
            // setPadding「left, top, right, bottom」:(pixel)

// この dringtone は Activity 内の表示コンポーネントではないので、
// この dringtone は Activity 内の Layout などに登録する必要はない。
            wdgBody = new Dialog(getContext( ));
            wdgBody.setContentView(layout);
            wdgBody.setTitle(title);
            wdgBody.setOnDismissListener(this);
            return wdgBody;
        }

        synchronized void MasterReflection( ){
            if (DEBUG) Log.i(tag, "RadioGroupDriver.MasterReflection( );");

            if( null!=wtvMaster ) {
                String rckey = getCheckedKey( );
                String title = "";
                RadioButton radio = null;
                if( null!=rckey ) {
                    radio = dm1oKeyRadio.get(rckey);
                }
                if( null!=radio ) {
                    title = radio.getText( ).toString( );
                }
                wtvMaster.setText(title);
            }
        }

        @Override
        public synchronized void onCheckedChanged(RadioGroup group, int checkId) {
            // super.onCheckedChanged(group, checkId);
            if (DEBUG) Log.i(tag, "RadioGroupDriver.onCheckedChanged( );");
            RadioButton radio = (RadioButton) findViewById(checkId);
            setRadioChecked(radio); // true; // false; //
            if (DEBUG) Log.i(tag, "onCheckedChanged( ) : " +
                    "getCheckedKey( )=" + getCheckedKey( ) + "; " +
                    "");
        }

        @Override
        public boolean onLongClick(View v) {
            // super.onLongClick(v);
            if (DEBUG) Log.i(tag, "RadioGroupDriver.onLongClick( );");

            lOnLongClick = true; // false; //
            if(DEBUG)Log.i(tag, "RadioGroupDriver.onLongClick( ) : "+
                    "lOnLongClick="+lOnLongClick+"; "+
                    "lLongtouchOn="+lLongtouchOn+"; "+
                    "");
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
            return ! lLongtouchOn; //false; // true; //
        }

        @Override
        public synchronized boolean onTouch(View v, MotionEvent event) {
            if (DEBUG) Log.i(tag, "RadioGroupDriver.onTouch( );");

            // String key = dm1oRadioKey.get(v);
            // switch(v.getId( ))
            switch(event.getAction( )) {
                case MotionEvent.ACTION_DOWN :
                    if (DEBUG) Log.i(tag, "RadioGroupDriver.onTouch( ) : "+
                            "MotionEvent.ACTION_DOWN; ");

                    lOnLongClick = false; // true; //
                    if(DEBUG)Log.i(tag, "MotionEvent.ACTION_DOWN; "+
                            "lOnLongClick="+lOnLongClick+"; "+
                            "lLongtouchOn="+lLongtouchOn+"; "+
                            "");
// 標準のイベント処理を実行させるために、意図的に false を返す。
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                    return false; // true; //
                // break;
                case MotionEvent.ACTION_UP :
                    if (DEBUG) Log.i(tag, "RadioGroupDriver.onTouch( ) : "+
                            "MotionEvent.ACTION_UP; ");
                    if(DEBUG)Log.i(tag, "MotionEvent.ACTION_UP : "+
                            "getCheckedKey( )="+getCheckedKey( )+"; "+
                            "");
                    if(DEBUG)Log.i(tag, "MotionEvent.ACTION_DOWN; "+
                            "lOnLongClick="+lOnLongClick+"; "+
                            "lLongtouchOn="+lLongtouchOn+"; "+
                            "");

                    if( ! lOnLongClick ||
                            lOnLongClick && lLongtouchOn ) {
                        lOnLongClick  = false; // true; //
                        setRadioChecked((RadioButton) v);
                        // MasterReflection( );
                        if( null!=wdgBody && lSelectorMode ) {
                            wdgBody.dismiss( );
                        }
                    }
// 標準のイベント処理を実行させるために、意図的に false を返す。
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                    return false; // true; //
                // break;
                case MotionEvent.ACTION_MOVE :
                    //
                    break;
                case MotionEvent.ACTION_CANCEL :
                    if (DEBUG) Log.i(tag, "RadioGroupDriver.onTouch( ) : "+
                            "MotionEvent.ACTION_CANCEL; ");
                    //
                    break;
                default :
                    if (DEBUG) Log.i(tag, "RadioGroupDriver.onTouch( ) : "+
                            "default; ");
                    //
            }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
            return false; // true; //
        }

        @Override
        public synchronized void onDismiss(DialogInterface dialog){
            // super.onDismiss(dialog);
            if (DEBUG) Log.i(tag, "RadioGroupDriver.onDismiss( );");

            if(DEBUG)Log.i(tag, "RadioGroupDriver.onDismiss( ) : "+
                    "getCheckedKey( )="+getCheckedKey( )+"; "+
                    "");
            MasterReflection( );
            if( null!=oDismissCallback ) {
                oDismissCallback.onDismiss(dialog);
            }
        }

    }

    public class RadioGroupDriver_StorageReference extends RadioGroupDriver {
// キーが null の場合は当然 使用不可、ただし キーが""(ゼロ・バイト長の文字列)なら使用可。
// データが null の場合は、データが存在しないと言う意味。
// 直に vsCheckedKey にアクセスしないで下さい。
        final String fsStorageRef_File;
        final String fsGroup_Key;
        // final String fsGroup_FirstKey = getClass().getSimpleName() + ".";
        // final String fsGroup_LastKey;
        public volatile StorageReference oStorageRef; // = new StorageReference( , );
        // volatile Context oSelfContext;

        public RadioGroupDriver_StorageReference(
                Context ctx, TextView master, int dcversion, String fkey, String glkey, boolean selector, boolean longtouch,
                int appearance, DialogInterface.OnDismissListener dismiss) {
            super(ctx, master, selector, longtouch, appearance, dismiss);
            synchronized(this) {
                fsStorageRef_File = fkey;
                fsGroup_Key = getClass().getSimpleName( )+"."+glkey+"/";
                oStorageRef = new StorageReference(
                        oAppContext,
                        dcversion, fsStorageRef_File,
                        oAppContext.MODE_PRIVATE);
            }
        }

        public synchronized void Save( ) {
            if (DEBUG) Log.i(tag, "RadioGroupDriver_SharedPreferences.Save( );");

            oStorageRef.Save( );
        }

        public synchronized void Load( ) {
            if (DEBUG) Log.i(tag, "RadioGroupDriver_SharedPreferences.Load( );");

            oStorageRef.Load( );
            String rckey = oStorageRef.getString(fsGroup_Key+fsRadioName_Key);
            setRadioCheckedKey(rckey); // true; // false; //
        }

        @Override
        public String getCheckedKey( ) {
            if (DEBUG) Log.i(tag, "RadioGroupDriver_SharedPreferences.getCheckedKey( );");

            return oStorageRef.getString(fsGroup_Key+fsRadioName_Key);
        }

        @Override
        void setCheckedKey(String rckey) {
            if (DEBUG) Log.i(tag, "RadioGroupDriver_SharedPreferences.setCheckedKey( );");

            // super.setCheckedKey(rckey);
            oStorageRef.put(fsGroup_Key+fsRadioName_Key, rckey);
        }

        public synchronized void setDefaultChecked(boolean  load) {
            if( load ) {
                Load();
            }
            super.setDefaultChecked( );
        }

        @Override
        public synchronized void onCheckedChanged(RadioGroup group, int checkId) {
            // super.onCheckedChanged(group, checkId);
            if (DEBUG) Log.i(tag, "RadioGroupDriver_SharedPreferences.onCheckedChanged( );");
            super.onCheckedChanged(group, checkId);
            oStorageRef.Save();
            if (DEBUG) Log.i(tag, "onCheckedChanged( ) : " +
                    "getCheckedKey( )=" + getCheckedKey( ) + "; " +
                    "");
        }

    }

    public class RadioGroupDriver_StorageRef_Ringtone extends RadioGroupDriver_StorageReference {

        boolean lSilent = false; // true; //
        // int iStreamType = -1;
        int iRingtoneType = -1;
        // int iVolumeType = -1;
        // String[] vsFExtension;
        public volatile HashMap<String, Boolean> dm1oFExtension = new HashMap<String, Boolean>( );
        // volatile:最適化の抑制.
        // public volatile ArrayList<String> dl1sIndexKey = new ArrayList<String>( );
        // public volatile List<String> di1sKey;
        // public volatile String[] d1sKey;
        public volatile HashMap<String, String> dm1sKeyTitle = new HashMap<String, String>( );

        // MediaPlayer oMediaPlayer;
        Ringtone oRingtone;

        public RadioGroupDriver_StorageRef_Ringtone(
                Context ctx, TextView master,
                int dcversion, String fkey, String glkey,
                boolean selector, boolean longtouch,
                int appearance, boolean silent, int tringtone, String[] lfextension,
                DialogInterface.OnDismissListener dismiss) {
            super(ctx, master, dcversion, fkey, glkey, selector, longtouch, appearance, dismiss);
            synchronized(this) {
                lSilent = silent;
                iRingtoneType = tringtone;
                for (int i = 0; i < lfextension.length; i++) {
                    dm1oFExtension.put(
                            lfextension[i].toLowerCase(), new Boolean(true)); // false; //
                }
                if(null!=oRingtone) {
                    if( oRingtone.isPlaying( ) ){
                        oRingtone.stop( );
                    }
                    oRingtone = null;
                }
                getCreateRadioGroup( );
            }
        }

        synchronized RadioGroupDriver_StorageRef_Ringtone getCreateRadioGroup( ) {
            if(DEBUG)Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.getCreateRadioGroup( );");
            String pstype001 = "media/audio/";
            String[] lfstype = new String[2];
            switch (iRingtoneType) {
                case AudioManager.STREAM_ALARM :
                    lfstype[0] = "Alarms";
                    lfstype[1] = pstype001+"alarms";
                    break;
                case AudioManager.STREAM_NOTIFICATION :
                    lfstype[0] = "Notifications";
                    lfstype[1] = pstype001+"notifications";
                    break;
                case AudioManager.STREAM_RING :
                    lfstype[0] = "Ringtones";
                    lfstype[1] = pstype001+"ringtones";
                    break;
                default:{
                    //
                } break;
            }
            String fpath, fname, ftype;
            File file;
            Uri uri;
            Ringtone ringtone;
            String title, key;
            ArrayList<String> dl1sKey = new ArrayList<String>( );
            int k = 0;
            for(int i = 0; i<lfstype.length; i++){
                file = new File(vsSDCardRoot,lfstype[i]);
                if( file.exists( ) ) {
                    File[] lfile = file.listFiles();
                    for (int j = 0; j<lfile.length; j++) {
                        if ( lfile[j].isFile( ) ) {
                            fpath = "";
                            fname = lfile[j].getName( );
                            ftype = FileOp.getFExtension(fname);
                            if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.getCreateRadioGroup( );" +
                                    "fname="+fname+"; "+
                                    "");
                            if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.getCreateRadioGroup( );" +
                                    "ftype="+ftype+"; "+
                                    "");
                            if( null!=dm1oFExtension.get(ftype.toLowerCase( )) ){
                                title = FileOp.rmvFExtension(fname);
                                if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.getCreateRadioGroup( );" +
                                        "title="+title+"; "+
                                        "");
                                try {
                                    fpath = lfile[j].getCanonicalPath( );
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.getCreateRadioGroup( );" +
                                        "fpath="+fpath+"; "+
                                        "");
                                key = fpath;
                                if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.getCreateRadioGroup( );" +
                                        "key="+key+"; "+
                                        "");
                                dl1sKey.add(key);
                                // addCreateRadio(key, title);
                                dm1sKeyTitle.put(key, title);
                                // k++;
                            }
                        }
                    }
                }
            }
            String[] d1sKey = dl1sKey.toArray(new String[0]);
            Arrays.sort(d1sKey, new Comparator<String>() {
                @Override
                public int compare(String vs0, String vs1) {
                    // このソートを Windows 的な File 名のソート順にカスタマイズしている.
                    String vs;
                    vs = vs0;
                    vs0 = vs.toUpperCase( )+fsAscTab+vs;
                    vs = vs1;
                    vs1 = vs.toUpperCase( )+fsAscTab+vs;
                    return vs0.compareTo(vs1);
                }
            });
            dl1sKey = new ArrayList<String>(Arrays.asList(d1sKey));
            if( lSilent ) {
                key = "";
                title = "<Silent>";
                dl1sKey.add(0, key);
                // addCreateRadio(key, title);
                dm1sKeyTitle.put(key, title);
            }
            if(DEBUG)Log.i(tag, "for(int i = 0; i<dl1sKey.size( ); i++);");
            for(int i = 0; i<dl1sKey.size( ); i++){
                key = dl1sKey.get(i);
                title = dm1sKeyTitle.get(key);
                if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.getCreateRadioGroup( );" +
                        "i="+i+"; "+
                        "key="+key+"; "+
                        "title="+title+"; "+
                        "");
                addCreateRadio(key, title);
            }
            // Load( );
            return this;
        }

        @Override
        @SuppressWarnings("deprecation")
         public synchronized boolean onLongClick(View v) {
            // super.onLongClick(v);
            if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.onLongClick( );");

            String key = dm1oRadioKey.get(v);
            if( null!=key && ! key.equals("") ) {
                try {
                    if(null!=oRingtone) {
                        if( oRingtone.isPlaying( ) ){
                            oRingtone.stop( );
                        }
                        oRingtone = null;
                    }
                    // setRadioCheckedKey(rckey);
                    // Uri uri = Uri.parse(getCheckedKey( ));
                    Uri uri = Uri.parse(key);
// StreamType の設定で「AudioManager.STREAM_MUSIC」を指定すると
// 「MediaPlayer、Ringtone」で音が鳴らない端末があるようです。
// 「MediaPlayer#setAudioStreamType( )」は使わずにデフォルト設定で使う事により、
// 「STREAM_MUSIC」になるようです。
// ただし「Ringtone#setStreamType( )」を使わずにデフォルト設定で使った場合は、
// 恐らくデフォルト値は「STREAM_RING」になると思われます。
// 「音楽:STREAM_MUSIC」以外の「アラーム:STREAM_ALARM、通知:STREAM_NOTIFICATION、
// 「着信:STREAM_RING、システム:STREAM_SYSTEM、通話:STREAM_VOICE_CALL」なら設定 可能だと思われます。
// ただし「Android 4.0」以上は「通知:STREAM_NOTIFICATION、着信:STREAM_RING」のボリュームが連動、
// 「Android 5.0」以上は「通知:STREAM_NOTIFICATION、着信:STREAM_RING、システム:STREAM_SYSTEM」
// のボリュームが連動するようです。
                    oRingtone = RingtoneManager.getRingtone(oAppContext, uri);
                    // oRingtone.setStreamType(AudioManager.STREAM_MUSIC);
                    oRingtone.play( );
                } catch (IllegalArgumentException e) {
                    e.printStackTrace();
                } catch (IllegalStateException e) {
                    e.printStackTrace();
                } catch (NullPointerException e) {
                    e.printStackTrace();
                }
            }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
            return super.onLongClick(v); //false; // true; //
        }

        @Override
        public synchronized boolean onTouch(View v, MotionEvent event) {
            // super.onTouch(v, event);
            if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.onTouch( );");

            // String key = dm1oRadioKey.get(v);
            // switch(v.getId( ))
            switch(event.getAction( )) {
                case MotionEvent.ACTION_DOWN :
                    if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.onTouch( ) : "+
                            "MotionEvent.ACTION_DOWN; ");
// 標準のイベント処理を実行させるために、意図的に false を返す。
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                    return super.onTouch(v, event); // false; // true; //
                    // break;
                case MotionEvent.ACTION_UP :
                    if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.onTouch( ) : "+
                            "MotionEvent.ACTION_UP; ");
                    if(null!=oRingtone) {
                        if( oRingtone.isPlaying( ) ){
                            oRingtone.stop( );
                        }
                        oRingtone = null;
                    }
// 標準のイベント処理を実行させるために、意図的に false を返す。
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                    return super.onTouch(v, event); // false; // true; //
                    // break;
                case MotionEvent.ACTION_MOVE :
                    //
                    break;
                case MotionEvent.ACTION_CANCEL :
                    if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.onTouch( ) : "+
                            "MotionEvent.ACTION_CANCEL; ");
                    //
                    break;
                default :
                    if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.onTouch( ) : "+
                            "default; ");
                    //
            }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
            return false; // true; //
        }

        @Override
        public synchronized void onDismiss(DialogInterface dialog){
            if (DEBUG) Log.i(tag, "RadioGroupDriver_StorageRef_Ringtone.onDismiss( );");
            if( null!=oRingtone ) {
                if( oRingtone.isPlaying( ) ){
                    oRingtone.stop();
                }
                oRingtone = null;
            }
            super.onDismiss(dialog);
        }

    }
    
}



『<Project名>→app→main→java→com.example.ringkitchentimer→FileOp(FileOp.java)』


package com.example.ringkitchentimer;

public class FileOp {
// 拡張子を含まない場合は、そのままです。
// ドットで始まるファイル名の場合は、そのままです。
    public static String rmvFExtension(String filename) {
        if (filename == null) {
            return null;
        }
        int point = filename.lastIndexOf('.');
        if (point<=0) {
            return filename;
        }
        return filename.substring(0, point);
    }

    public static String getFExtension(String filename) {
// 返される拡張子は"."(ドット)を含みます。
// 拡張子を含まない場合は、空文字を返します。
// ドットで始まるファイル名の場合は、空文字を返します。
        if (filename == null) {
            return null;
        }
        int point = filename.lastIndexOf(".");
        if (point<=0) {
            return "";
        }
        return filename.substring(point);
    }

}



『<Project名>→app→main→java→com.example.ringkitchentimer→StorageReference(StorageReference.java)』


package com.example.ringkitchentimer;

import android.content.Context;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.FileNotFoundException;
import java.io.EOFException;
import java.io.UnsupportedEncodingException;
import java.io.IOException;

import java.util.HashMap;

public class StorageReference implements CommonDefine {

    // Context oBaseContext = MainActivity.oBaseContext;
    Context oAppContext; // Application Context.
    final int fiDataCompatibleVersion;
    final String fsRefFile;
    final int iMode;
    volatile HashMap<String, Object> dm1oRefBuffer; // = new HashMap<String, Object>( );

    public StorageReference(Context actx, int dcversion, String file, int mode) {
        synchronized(this){
            oAppContext = actx;
            fiDataCompatibleVersion = dcversion;
            fsRefFile = file;
            iMode = mode;
            // Load( );
        }
    }

    public synchronized void put(String key, Object value) {
        dm1oRefBuffer.put(key, value);
    }

    public synchronized Object getObject(String key) {
        return dm1oRefBuffer.get(key);
    }

    public synchronized String getString(String key) {
        return (String) dm1oRefBuffer.get(key);
    }

    public synchronized Integer getInteger(String key) {
        return (Integer) dm1oRefBuffer.get(key);
    }

    public synchronized void Save( ) {
        try {
            File vsFFDir = oAppContext.getFilesDir();
            if (DEBUG) Log.i(tag,
                    "vsFFDir.getCanonicalPath()=" + vsFFDir.getCanonicalPath() + "; " +
                            "");
            // Context の openFileOutput は内部ストレージのアプリ固有の領域にアクセスされるらしい。
            // Context.MODE_PRIVATE:PRIVATE 出力モード:他のアプリからはアクセスできない。
            // Context.MODE_WORLD_READABLE:他のアプリケーションからの読込み可。
            // Context.?MODE_WORLD_WRITEABLE:他のアプリケーションからの書込み可。
            // MODE_WORLD_READABLEとMODE_WORLD_WRITEABLEを「|」で連結して同時に指定できる。
            FileOutputStream oFOS = oAppContext.openFileOutput(fsRefFile, iMode);
            BufferedOutputStream oBOS = new BufferedOutputStream(oFOS);
            ObjectOutputStream oOOSSerial = new ObjectOutputStream(oBOS);
            // Android の場合「〜.getText( )」の返り値の実態は String ではなく CharSequence となっている。
            // CharSequence は どうも Serializable ではないようで、
            // そのまま OutputStream で Write しようとすると、
            // NotSerializableException が発生してしまうようだ。
            // oOOSSerial.writeObject(cetMessage.getText( ).toString( ));
            String[] keys = dm1oRefBuffer.keySet().toArray(new String[0]);
            Object value;
            oOOSSerial.writeInt(fiDataCompatibleVersion);
            for (int i = 0; i < keys.length; i++) {
// ↑どうも Android では「for( String key : 〜.keySet( ) )」のような「拡張 for 文」や、
// Iterator なども実行時エラーになるようだ。
                value = dm1oRefBuffer.get(keys[i]);
                if (null != value) {
                    oOOSSerial.writeObject(keys[i]);
                    oOOSSerial.writeObject(value);
                    if(DEBUG)Log.i(tag, "StorageReference.Save( ) : "+
                            "keys[i]="+keys[i]+"; "+
                            "value="+value+"; "+
                            "");
                }
            }
            oOOSSerial.close();
            File vsFSPath = oAppContext.getFileStreamPath(fsRefFile);
            if (DEBUG) Log.i(tag,
                    "vsFSPath.getCanonicalPath()=" + vsFSPath.getCanonicalPath() + "; " +
                            "");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public synchronized void Load() {
        if (DEBUG) Log.i(tag, "StorageReference.Load( );");

        try {
            dm1oRefBuffer = new HashMap<String, Object>( );
            File vsFFDir = oAppContext.getFilesDir();
            if (DEBUG) Log.i(tag,
                    "vsFFDir.getCanonicalPath()=" + vsFFDir.getCanonicalPath() + "; " +
                            "");
            File vsFSPath = oAppContext.getFileStreamPath(fsRefFile);
            if (DEBUG) Log.i(tag,
                    "vsFSPath.getCanonicalPath()=" + vsFSPath.getCanonicalPath() + "; " +
                            "");
            FileInputStream oFIS = oAppContext.openFileInput(fsRefFile);
            BufferedInputStream oBIS = new BufferedInputStream(oFIS);
            ObjectInputStream oOISSerial = new ObjectInputStream(oBIS);
            String key;
            Object value;
            int dcversion = oOISSerial.readInt( );
            if( dcversion==fiDataCompatibleVersion ) {
                do {
                    key = (String) oOISSerial.readObject();
                    if (null == key) break;
                    value = oOISSerial.readObject();
                    dm1oRefBuffer.put(key, value);
                    if (DEBUG) Log.i(tag, "StorageReference.Load( ) : " +
                            "key=" + key + "; " +
                            "value=" + value + "; " +
                            "");
                } while (true); // false; //
            }
            oOISSerial.close();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            // e.printStackTrace();
        } catch (EOFException e) {
            // e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}



『<Project名>→app→main→java→com.example.ringkitchentimer→MyService(MyService.java)』


package com.example.ringkitchentimer;

import android.os.IBinder;
import android.content.Context;
import android.content.Intent;
import android.app.PendingIntent;
import android.app.Service;
import android.app.Notification;
import android.support.v7.app.NotificationCompat;
import android.util.Log;

import java.util.Timer;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MyService extends Service implements CommonDefine{

    static final int ONGOING_NOTIFICATION = 1;

    static Object oAnchor_TimerTask_Array;
    // ↑ただのアンカーなので、アクセスする場合は
    // MyTimerTask の方の dm1oTimer をアクセスして下さい。

    // 下記「timer、delay、interval」は あくまでもテンポラリー。
    Timer timer; // = new Timer( );
    long delay; // ms
    long interval; // ms

    @Override
    public void onCreate( ){
        super.onCreate( );
        if (DEBUG) Log.i(tag, "MyService.onCreate( );");
    }

    @Override
    public int onStartCommand(Intent si, int f, int sid) {
        super.onStartCommand(si, f, sid);
        if(DEBUG)Log.i(tag, "MyService.onStartCommand( ) : "+
                "fsDebugCrest="+fsDebugCrest+"; "+
                "");

// 良く分からないが、次の PendingIntent は
// Activity の取得の場合は「getActivity(〜)」、
// Activity の取得 以外は「getService(〜)」になると思われる。
        SettingForeground(PendingIntent.getActivity(
                this, 0, new Intent(this, MainActivity.class),
                PendingIntent.FLAG_CANCEL_CURRENT));

        return START_NOT_STICKY;
// START_NOT_STICKY:
// サービスを起動するペンディングインテントが存在しない限りサービスは再起動されません 。
// 強制終了によりサービスが終了した場合、勝手な再起動を防ぐ場合にはこれを使用します。
// システムからの勝手な再起動を考慮せずに作成されたサービスにおいて、
// システムからの勝手な再起動を許すと、
// それがバグとなりサービスに異常をきたす事になりなりかねません。
    }

    @Override
    public IBinder onBind(Intent si) {
        // super.onBind(si);
        return null;
    }

    public void SettingForeground(PendingIntent pi) {
        if(DEBUG)Log.i(tag, "MyService.SettingForeground( );");
        Notification notif = new NotificationCompat.Builder(this)
                .setContentIntent(pi)
                .setAutoCancel(true)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("Content Text.")
                // .setContentInfo("Content Info.")
                // .setTicker("Ticker.")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.drawable.x_slogo_android031) // SmallIcon を設定しないと通知されない(例外も発生しない)
                .build( );
        startForeground(ONGOING_NOTIFICATION, notif);
    }

    @Override
    public void onDestroy( ){
        super.onDestroy( );
        if (DEBUG) Log.i(tag, "MyService.onDestroy( );");

        oAnchor_TimerTask_Array = null;
        stopForeground(true);
    }

}



『<Project名>→app→main→java→com.example.ringkitchentimer→MyFSTimerTask(MyFSTimerTask.java)』


package com.example.ringkitchentimer;

import android.content.Context;
import android.content.Intent;
import java.util.Timer;
import java.util.TimerTask;
import android.util.Log;

import java.text.SimpleDateFormat;
import java.util.Date;

import java.util.HashMap;

public abstract class MyFSTimerTask extends TimerTask implements CommonDefine {

    static Context oAppContext; // Application Context.
    // volatile:最適化の抑制.
    public volatile String vsName = "";
    public volatile Timer oTimer;
    public volatile boolean lTimerRunning = false; // true; //
    public volatile static HashMap<String, MyFSTimerTask> dm1oTimerTask = new HashMap<String, MyFSTimerTask>( );

    public MyFSTimerTask(Context actx, Timer t, String vsN){
        super( );
        if (DEBUG) Log.i(tag, "MyTimerTask( );");

        synchronized(this) {
            lTimerRunning = true; // false; //
            vsName = vsN;
            oTimer = t;
            TaskRemove(vsName);
            dm1oTimerTask.put(vsName, this);

            oAppContext = actx;
            Intent si = new Intent(oAppContext, MyService.class);
            // si.putExtra("SubjectKey", "Foreground");
            oAppContext.startService(si);
            MyService.oAnchor_TimerTask_Array = dm1oTimerTask;
        }
        if(DEBUG)Log.i(tag, "MyTimerTask( ) : "+
                "vsName="+vsName+"; "+
                "dm1oTimerTask.size( )="+dm1oTimerTask.size( )+"; "+
                "");
    }

    public static synchronized void TaskCancel(Context actx, String vsN){
        if (DEBUG) Log.i(tag, "MyTimerTask.TaskCancel( );");

        TaskRemove(vsN);
        Check_ServiceCancel(actx);
    }

    private static synchronized void TaskRemove(String vsN){
        if (DEBUG) Log.i(tag, "MyTimerTask.TaskRemove( );");

        MyFSTimerTask timertask = dm1oTimerTask.get(vsN);
        if( null!=timertask ){
            timertask.oTimer.cancel( );
            dm1oTimerTask.remove(vsN);
        }
    }

    private static synchronized void Check_ServiceCancel(Context actx){
        if (DEBUG) Log.i(tag, "MyTimerTask.Check_ServiceCancel( );");

        if( 0==dm1oTimerTask.size( ) ){
            if( null!=actx ){
                oAppContext = actx;
            }
            Intent si = new Intent(oAppContext, MyService.class);
            oAppContext.stopService(si);
        }
    }

}



『<Project名>→app→main→java→com.example.ringkitchentimer→MyFSTimerTask_Sound(MyFSTimerTask_Sound.java)』


package com.example.ringkitchentimer;

import android.content.Context;
import java.util.Timer;
import android.media.MediaPlayer;
import android.media.Ringtone;
import android.media.SoundPool;

public abstract class MyFSTimerTask_Sound extends MyFSTimerTask {

    int iSoundId;
    // volatile:最適化の抑制.
    // public volatile int iPoolId = -1;
    public volatile boolean lSoundRunning = false; // true; //
    // public volatile MediaPlayer oMediaPlayer;
    public volatile Ringtone oRingtone;
    // public volatile SoundPool oSoundPlayer;

    public MyFSTimerTask_Sound(Context actx, Timer t, String vsN, int iSI){
        super(actx, t, vsN);

        // lSoundRunning = true; // false; //
        iSoundId = iSI;
    }
}



『<Project名>→app→main→res→drawable→border.xml』(ファイルを作成)
※「EditText」の枠として使っている。


<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ffffff" />
    <padding android:top="3dp" android:bottom="3dp"
        android:left="7dp" android:right="7dp" />
    <stroke android:width="1px" android:color="#000000" />
    <corners android:radius="5dp" />
</shape>



『<Project名>→app→main→res→drawable→button_style.xml』(ファイルを作成)
※「TextView」をボタン風に表示している。


<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/ButtonBackground"/>
            <corners android:radius="3dp" />
            <stroke android:width="2dp" android:color="@color/ButtonShadow" />
            <!-- 「stroke android:width=」は ずらす幅より少し大きな値にする。 -->
        </shape>
    </item>
    <item android:bottom="1.45dp" android:right="1.47dp">
        <shape android:shape="rectangle">
            <solid android:color="@color/ButtonBackground"/>
            <padding android:top="4.4dp" android:bottom="4.4dp"
                android:left="5dp" android:right="5dp" />
            <corners android:radius="3dp" />
        </shape>
    </item>
</layer-list>



『<Project名>→app→main→res→layout→activity_main.xml』


<?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"
    tools:context=".MainActivity"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="#ffffff"
    android:orientation="vertical" >

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/actionBarSize"
        android:background="@color/Background_Cornflowerblue"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:stretchColumns="0,1" >

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text="Ringtone"
                android:id="@+id/wtvRingtone"
                android:layout_column="0"
                android:gravity="center_horizontal"
                android:layout_gravity="center_horizontal" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text="Volume"
                android:id="@+id/wtvVolume"
                android:layout_column="1"
                android:gravity="center_horizontal"
                android:layout_gravity="center_horizontal" />

        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

<!-- ボタンは左右にムダなスペースがあるので、表示幅が Tight でボタンが表示できない場合、-->
<!-- TextView をボタン・スタイルで表示させれば良い。 -->
            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text=""
                android:drawableRight="@drawable/xic_menu_moreoverflow_focused_holo_light0272"
                android:layout_marginTop = "6dp"
                android:layout_marginBottom = "6dp"
                android:layout_marginLeft = "3.5dp"
                android:layout_marginRight = "3.5dp"
                android:background="@drawable/button_style"
                android:layout_column="0"
                android:gravity="center_horizontal"
                android:textSize="22dp"
                android:id="@+id/wtvbRingtone" />

<!-- ボタンは左右にムダなスペースがあるので、表示幅が Tight でボタンが表示できない場合、-->
<!-- TextView をボタン・スタイルで表示させれば良い。 -->
            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text=""
                android:drawableRight="@drawable/xic_menu_moreoverflow_focused_holo_light0272"
                android:layout_marginTop = "6dp"
                android:layout_marginBottom = "6dp"
                android:layout_marginLeft = "3.5dp"
                android:layout_marginRight = "3.5dp"
                android:background="@drawable/button_style"
                android:layout_column="1"
                android:gravity="center_horizontal"
                android:textSize="22dp"
                android:id="@+id/wtvbVolume" />

        </TableRow>

        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </TableRow>
    </TableLayout>

        <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin = "7dp"
        android:background="@drawable/border"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:id="@+id/wetSound"
        android:inputType="textMultiLine"
        android:lines="2"
        android:minLines="2"
        android:maxLines="2"
        android:editable="false"
        android:clickable="false"
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:cursorVisible="false"
        android:textIsSelectable="false"
        android:longClickable="false"
        android:text="Sound" />

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

            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text="Start"
                android:id="@+id/wbtStart"
                android:layout_weight="1" />


            <Button
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceLarge"
                android:text="Cancel"
                android:id="@+id/wbtCancel"
                android:layout_weight="1" />
        </LinearLayout>

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

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="時間"
            android:id="@+id/wtvHour"
            android:layout_weight="1"
            android:gravity="center_horizontal"
            android:textColor="@android:color/black" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="分"
            android:id="@+id/wtvMinute"
            android:layout_weight="1"
            android:gravity="center_horizontal"
            android:textColor="@android:color/black" />

        <TextView
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:text="秒"
            android:id="@+id/wtvSecond"
            android:layout_weight="1"
            android:gravity="center_horizontal"
            android:textColor="@android:color/black" />
    </LinearLayout>

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

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:inputType="numberDecimal"
            android:ems="10"
            android:id="@+id/wetHour"
            android:layout_weight="1"
            android:gravity="right" />

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:inputType="numberDecimal"
            android:ems="10"
            android:id="@+id/wetMinute"
            android:layout_weight="1"
            android:gravity="right" />

        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:inputType="numberDecimal"
            android:ems="10"
            android:id="@+id/wetSecond"
            android:layout_weight="1"
            android:gravity="right" />
    </LinearLayout>

        <EditText
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:inputType="textMultiLine"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:layout_margin = "7dp"
        android:background="@drawable/border"
        android:id="@+id/wetMessage"
        android:editable="false"
        android:clickable="false"
        android:focusable="false"
        android:focusableInTouchMode="false"
        android:cursorVisible="false"
        android:textIsSelectable="false"
        android:longClickable="false"
        android:layout_weight="1" />

</LinearLayout>



『<Project名>→app→src→main→res→menu→main.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<menu
    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"
    tools:context=".MainActivity">

    <item android:id="@+id/menu_main_settings"
        android:title="Settings"
        android:icon="@drawable/ic_menu_moreoverflow_normal_holo_light"
        app:showAsAction="always">
        <!-- ↑ showAsAction:Toolbar に対する表示設定。 -->
        <!-- "always":常に表示、"never":常に表示しない、"ifRoom":表示する余裕があれば表示。 -->
        <menu>
            <item android:id="@+id/menu_main_settings_item1"
                android:title="item1">
            </item>

            <item android:id="@+id/menu_main_settings_item2"
                android:title="item2">
            </item>

            <item android:id="@+id/menu_main_settings_item3"
                android:title="item3">
            </item>
        </menu>
    </item>

</menu>



『<Project名>→app→src→main→res→values→styles.xml』


<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>



『<Project名>→app→src→main→res→values→colors.xml』


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>

    <color name="holo_blue_light">#33b5e5</color>
    <color name="Background_Cornflowerblue">#6495ED</color>
    <color name="Background_MediumslateblueLike">#9b7fff</color>

    <color name="ButtonBackground">#d7d7d7</color>
    <color name="ButtonShadow">#c6c6c6</color>
    <color name="GrayOutText">#909090</color>
    <color name="GrayOutText002">#888888</color>
    <color name="GrayOutText003">#777777</color>
    <color name="SettingMenuBackground">#eeeeee</color>

    <color name="black">#000000</color>
    <color name="gray">#808080</color>
    <color name="silver">#c0c0c0</color>
    <color name="white">#ffffff</color>
    <color name="maroon">#800000</color>
    <color name="red">#ff0000</color>
    <color name="purple">#800080</color>
    <color name="fuchsia">#ff00ff</color>
    <color name="magenta">#ff00ff</color>
    <color name="green">#008000</color>
    <color name="lime">#00ff00</color>
    <color name="olive">#808000</color>
    <color name="yellow">#ffff00</color>
    <color name="navy">#000080</color>
    <color name="blue">#0000ff</color>
    <color name="teal">#008080</color>
    <color name="aqua">#00ffff</color>
    <color name="cyan">#00ffff</color>

    <color name="pink">#ffc0cb</color>
    <color name="orange">#ffa500</color>
    <color name="greenyellow">#adff2f</color>
    <color name="yellowgreen">#9acd32</color>
</resources>



『<Project名>→app→main→AndroidManifest.xml』


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

    <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:launchMode="singleInstance" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="MyService" >
        </service>
    </application>

</manifest>


<Number>: [00000A4A]  <Date>: 2016/05/08 16:48:41
<Title>: Empty 002
<Name>: amanojaku@管理人



ActionBarActivity が非推奨になったので、AppCompatActivity を作ってみたが、Dialog の UI Widget は古い Style のままなので AppCompatActivity の新しい Style の UI Widget とは UI に一貫性がない。
(他の方法がワカランので)非推奨の ActionBarActivity を使う事で とりあえずは古い Style の UI Widget に合わせられる。

《参考》

TextViewに枠線を付ける&背景色を変更する - yokkongの日記
http://d.hatena.ne.jp/yokkong/20111116/1321425474

【Android開発】スタイルにborderがないけど枠線を表示したい
http://se-suganuma.blogspot.jp/2010/02/androidborder.html

AppCompat
http://artemis.rosx.net/sjis/smt.cgi?r+izanami/&bid+0000090D&tsn+000009D3.&


とりあえず、[New Project]で(1ページ目)[Application name]:「Empty」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。

『<Project名>→app→main→res→drawable-hdpi』フォルダー(存在しない場合は作成してやる)内に「Android SDK」のデフォルト・アイコン「ic_menu_moreoverflow_normal_holo_light.png」をコピーする。
一応、「アプリの Logo マーク」を Android のマスコット・キャラの「Droid君」にしている。
下の方に『<Project名>→app→main→res→drawable-hdpi』フォルダー用の画像ファイル「アプリの Logo マーク:「48x48」Pixel、その他"TextView"用アイコンをアップしてあります。
"drawable-mdpi"フォルダー用は手抜きして作ってないが、解像度が低い端末でも自動的に その端末の解像度に合うように縮小して表示される。
その場合、当然 画質は悪くなるので画質を綺麗にしたい場合は手抜きせずにチャント"drawable-mdpi"フォルダー用の画像ファイルを作成してやれば良い。
「Toolbar」の UI はスタンダードな「ActionBarActivity」のような感じになっている。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。


『<Project名>→app→main→java→com.example.empty→MainActivity(MainActivity.java)』


package com.example.empty;

import android.os.Bundle;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.KeyEvent;
import android.os.Environment;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.Context;
import android.content.res.Resources;
import android.content.Intent;
import android.app.PendingIntent;
import android.app.Activity;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
// import android.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.util.Log;

import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity implements
        View.OnClickListener {
    static final String fsDebugCrest = " 002";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    DisplayMetrics oDisplayMetrics;
    public static int iPhysicalWidth, iPhysicalHeight;
    float vfDisplayMetricsDensity;

    MainActivity oMainApp;
    Context oBaseContext;
    Context oAppContext; // Application Context.
    Context oContext; // Activity Context.
    String vsToolbarTitle;
    Menu oMainMenu;

    String vsPackageName;
    String vsVersionName;
    int iVersionCode;

    TextView wtvMessage;
    static final int[] WIDGETS =  new int[]{
            R.id.textView1, R.id.textView2,
            R.id.button1, R.id.checkBox1, R.id.toggleButton1, R.id.radioButton1 };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (DEBUG) Log.i(tag, "Activity.onCreate( );");
        setContentView(R.layout.activity_main);
        oMainApp = this;
// System 共通の Context:別の Application とやりとりするとき。
        oBaseContext = getBaseContext();
// Application 固有の Context(Application ごとに Context が変化する):Application 共通のモノが対象。
        oAppContext = getApplicationContext();
// Activity 固有の Context(Activity ごとに Context が変化する):Activity に依存モノが対象。
        oContext = this;
        oDisplayMetrics = oAppContext.getResources( ).getDisplayMetrics( );
        vfDisplayMetricsDensity = oDisplayMetrics.density;
        iPhysicalWidth = oDisplayMetrics.widthPixels;
        iPhysicalHeight = oDisplayMetrics.heightPixels;
        if (DEBUG) Log.i(tag, "Activity.onCreate( ) : " +
                "vfDisplayMetricsDensity=" + vfDisplayMetricsDensity + "; " +
                "iPhysicalWidth=" + iPhysicalWidth + "; " +
                "iPhysicalHeight=" + iPhysicalHeight + "; " +
                "");

        vsPackageName = getPackageName();
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(
                    getPackageName(), PackageManager.GET_META_DATA);
            // 「<Project名>→app→build.gradle」ファイル内の
            // 「versionName、versionCode」の設定値を取得。
            vsVersionName = packageInfo.versionName;
            iVersionCode = packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }

        wtvMessage = (TextView) findViewById(R.id.wtvMessage);
        wtvMessage.append("fsDebugCrest=" + fsDebugCrest + ";\n" +
                "PackageName=" + vsPackageName + ";\n" +
                "VersionName=" + vsVersionName + ";\n" +
                "RevisionNo=" + String.valueOf(iVersionCode) + ";\n" +
                "vfDisplayMetricsDensity=" + vfDisplayMetricsDensity + ";\n" +
                "iPhysicalWidth=" + iPhysicalWidth + ";\n" +
                "iPhysicalHeight=" + iPhysicalHeight + "\n" +
                "");

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        toolbar.inflateMenu(R.menu.main);
        toolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                // メニューのクリック処理
                if (DEBUG) Log.i(tag, "toolbar.onMenuItemClick( );");
                return oMainApp.onOptionsItemSelected(item);
            }
        });

        // actionbar.setIcon(R.drawable.ic_menu_info_details);
        toolbar.setLogo(R.drawable.x_logo_android022);
        // ↑ 実際は Logo マークはアプリ固有の Icon にすべきモノです。
        // アプリ固有の Logo マークと言われてもイメージが湧かない方は
        // ブラウザの Icon(Logo マーク)をイメージしてみて下さい。
        // 下記サイトに主要ブラウザの Icon(Logo マーク)が掲載されています。
        // http://freesoft-100.com/pasokon/browser.html
        // ちなみに Toolbar に表示する Icon サイズのスタンダードは
        // "drawable-mdpi"フォルダー用は「32x32」、
        // "drawable-hdpi"フォルダー用は「48x48」だと思われます。

        vsToolbarTitle = "  " + getString(R.string.app_name) +
                fsDebugCrest +
                " V" + vsVersionName + // Version.
                " R" + String.valueOf(iVersionCode); // Revision.
// ↑ Toolbar に Logo マークを表示させるとタイトルと Logo マークとの間に隙間がないので、
// タイトルの先頭に半角スペースを2つ入れている。
        toolbar.setTitle(vsToolbarTitle);

        for (int iWId : WIDGETS) {
            View wvw = findViewById(iWId);
            wvw.setOnClickListener(this);
        }
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        if (DEBUG) Log.i(tag, "Activity.onRestart( );");
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (DEBUG) Log.i(tag, "Activity.onStart( );");
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (DEBUG) Log.i(tag, "Activity.onResume( );");
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (DEBUG) Log.i(tag, "Activity.onPause( );");
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (DEBUG) Log.i(tag, "Activity.onStop( );");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (DEBUG) Log.i(tag, "Activity.onDestroy( );");
    }

    @Override
    public void onClick(View v) {
        // super.onClick(v);
        if(DEBUG) Log.i(tag,"Activity.onClick( );");
        switch(v.getId()) {
            case  R.id.textView1 :
                if (DEBUG) Log.i(tag, "textView1");

                break;
            case  R.id.textView2 :
                if (DEBUG) Log.i(tag, "textView2");

                break;
            case R.id.button1 :
                if (DEBUG) Log.i(tag, "button1");

                break;
            case R.id.checkBox1 :
                if (DEBUG) Log.i(tag, "checkBox1");

                break;
            case R.id.toggleButton1 :
                if (DEBUG) Log.i(tag, "toggleButton1");

                break;
            case R.id.radioButton1 :
                if (DEBUG) Log.i(tag, "radioButton1");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
                return;
        }
        return;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        // アクションバー内で使用する為のメニューアイテムを実体化
        MenuInflater inflater = getMenuInflater();
        // MenuInflater inflater = getSupportMenuInflater();
        inflater.inflate(R.menu.main, menu);
        // 「R.menu.main」の「menu.main」は 恐らく「res」からの「フォルダー名+ファイル名」
        oMainMenu = menu;
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        if(DEBUG) Log.i(tag,"Activity.onOptionsItemSelected( );");
        switch(item.getItemId()) {
            case R.id.menu_main_settings :
                if (DEBUG) Log.i(tag, "menu_main_settings");

                break;
            case R.id.menu_main_settings_item1 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item1");

                break;
            case R.id.menu_main_settings_item2 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item2");

                break;
            case R.id.menu_main_settings_item3 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item3");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                return false; // true; //
        }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        super.dispatchKeyEvent(event);
        if (DEBUG) Log.i(tag, "Activity.dispatchKeyEvent( );");
        final int action = event.getAction();
        final int keyCode = event.getKeyCode();
        switch (keyCode) {
            case KeyEvent.KEYCODE_MENU:
                if (DEBUG) Log.i(tag, "KeyEvent.KEYCODE_MENU;");
                if (action == KeyEvent.ACTION_UP) {
                    if (DEBUG) Log.i(tag, "KeyEvent.ACTION_UP;");
                    if (oMainMenu != null) {
                        if (DEBUG) Log.i(tag, "oMainMenu.performIdentifierAction( );");
                        oMainMenu.performIdentifierAction(R.id.menu_main_settings, 0);
                    }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
                    return true; // false; //
                }
                break;

            default:
                if (DEBUG) Log.i(tag, "KeyEvent:Default");

                break;
        }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
        return false; // true; //
    }

}



『<Project名>→app→main→res→drawable→border.xml(ファイルを作成)』
※「UI Widget」の枠として使用する。


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ffffff" />
    <padding android:top="3dp" android:bottom="3dp"
        android:left="7dp" android:right="7dp" />
    <stroke android:width="1px" android:color="#000000" />
    <corners android:radius="5dp" />
</shape>



『<Project名>→app→main→res→drawable→button_style.xml(ファイルを作成)』
※「TextView」をボタン・スタイルで表示させている。


<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/ButtonBackground"/>
            <corners android:radius="3dp" />
            <stroke android:width="2dp" android:color="@color/ButtonShadow" />
            <!-- 「stroke android:width=」は ずらす幅より少し大きな値にする。 -->
        </shape>
    </item>
    <item android:bottom="1.45dp" android:right="1.47dp">
        <shape android:shape="rectangle">
            <solid android:color="@color/ButtonBackground"/>
            <padding android:top="4.4dp" android:bottom="4.4dp"
                android:left="5dp" android:right="5dp" />
            <corners android:radius="3dp" />
        </shape>
    </item>
</layer-list>



『<Project名>→app→main→res→layout→activity_main.xml』


<?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"
    tools:context=".MainActivity"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
        android:minHeight="?attr/actionBarSize"
        android:background="@color/Background_Cornflowerblue"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_margin = "7dp"
        android:background="@drawable/border" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="top"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:id="@+id/wtvMessage" />

    </ScrollView>

    <!-- ボタンは左右にムダなスペースがあるので、表示幅が Tight でボタンが表示できない場合、-->
    <!-- TextView をボタン・スタイルで表示させれば良い。 -->
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:textColor="@color/black"
            android:layout_marginTop = "6dp"
            android:layout_marginBottom = "6dp"
            android:layout_marginLeft = "3.5dp"
            android:layout_marginRight = "3.5dp"
            android:background="@drawable/button_style"
            android:text="TextView1"
            android:id="@+id/textView1" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:textColor="@color/black"
            android:layout_marginTop = "6dp"
            android:layout_marginBottom = "6dp"
            android:layout_marginLeft = "3.5dp"
            android:layout_marginRight = "3.5dp"
            android:background="@drawable/button_style"
            android:drawableRight="@drawable/xic_menu_moreoverflow_focused_holo_light0272"
            android:text="TextView2"
            android:id="@+id/textView2" />
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:gravity="center">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:text="Button1"
            android:id="@+id/button1" />

        <ToggleButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:text="ToggleButton1"
            android:id="@+id/toggleButton1" />
    </LinearLayout>

    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="22dp"
        android:text="CheckBox1"
        android:id="@+id/checkBox1" />

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="22dp"
        android:layout_gravity="center_horizontal"
        android:text="RadioButton1"
        android:id="@+id/radioButton1" />

</LinearLayout>



『<Project名>→app→src→main→res→menu→main.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<menu
    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"
    tools:context=".MainActivity">

    <item android:id="@+id/menu_main_settings"
        android:title="Settings"
        android:icon="@drawable/ic_menu_moreoverflow_normal_holo_light"
        app:showAsAction="always">
        <!-- ↑ showAsAction:Toolbar に対する表示設定。 -->
        <!-- "always":常に表示、"never":常に表示しない、"ifRoom":表示する余裕があれば表示。 -->
        <menu>
            <item android:id="@+id/menu_main_settings_item1"
                android:title="item1">
            </item>

            <item android:id="@+id/menu_main_settings_item2"
                android:title="item2">
            </item>

            <item android:id="@+id/menu_main_settings_item3"
                android:title="item3">
            </item>
        </menu>
    </item>

</menu>



『<Project名>→app→src→main→res→values→colors.xml』


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>

    <color name="holo_blue_light">#33b5e5</color>
    <color name="Background_Cornflowerblue">#6495ED</color>
    <color name="Background_MediumslateblueLike">#9b7fff</color>

    <color name="ButtonBackground">#d7d7d7</color>
    <color name="ButtonShadow">#c6c6c6</color>
    <color name="GrayOutText001">#909090</color>
    <color name="GrayOutText002">#888888</color>
    <color name="GrayOutText003">#777777</color>
    <color name="SettingMenuBackground">#eeeeee</color>

    <color name="black">#000000</color>
    <color name="gray">#808080</color>
    <color name="silver">#c0c0c0</color>
    <color name="white">#ffffff</color>
    <color name="maroon">#800000</color>
    <color name="red">#ff0000</color>
    <color name="purple">#800080</color>
    <color name="fuchsia">#ff00ff</color>
    <color name="magenta">#ff00ff</color>
    <color name="green">#008000</color>
    <color name="lime">#00ff00</color>
    <color name="olive">#808000</color>
    <color name="yellow">#ffff00</color>
    <color name="navy">#000080</color>
    <color name="blue">#0000ff</color>
    <color name="teal">#008080</color>
    <color name="aqua">#00ffff</color>
    <color name="cyan">#00ffff</color>

    <color name="pink">#ffc0cb</color>
    <color name="orange">#ffa500</color>
    <color name="greenyellow">#adff2f</color>
    <color name="yellowgreen">#9acd32</color>
</resources>



『<Project名>→app→main→AndroidManifest.xml』


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

    <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:launchMode="singleInstance" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


<Number>: [00000A54]  <Date>: 2016/05/08 21:27:17
<Title>: SDCard 001
<Name>: amanojaku@管理人



内部ストレージの Path を取得する(外部ストレージの Path も取得できるハズだがテストはしてない)。

【android】 SDカードのpathを取得する方法  一番かんたんなJava入門
http://nobuo-create.net/sdcard-2/

> Environment.getExternalStorageDirectory()
> このメソッドを使えば、外部ストレージ(SDカード)のマウント先を簡単に取得することができていました。
>  しかし、いつの頃からか、androidのとんでもない仕様変更により、この、名前からしていかにも外部ストレージのディレクトリをゲットしてくれそうな「Environment.getExternalStorageDirectory()」が、とんでもないトラップになってしまいました。それも結構悪質な。
>  どんな悪質な罠か、結論からと申しますと、このメソッドで取得できるのは外部ストレージ(SDカード)のpathではなく、内部ストレージのpathなんです。

[Android] スマホのSDカードパスを取得、機種依存対策
https://akira-watson.com/android/sdcard_path.html

> if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD)

この条件からすると「Build.VERSION_CODES.GINGERBREAD(Android 2.3)」以降から仕様変更されたと思われます。
もし「Android 2.3」未満もサポートしたい場合は「Environment.getExternalStorageDirectory()」は使えないと言う事になるので、これらのサイトを参考にして「Environment.getExternalStorageDirectory()」を使わずに内部ストレージの Path を取得する。


《参考》

【android】 SDカードのpathを取得する方法  一番かんたんなJava入門
http://nobuo-create.net/sdcard-2/

[Android] スマホのSDカードパスを取得、機種依存対策
https://akira-watson.com/android/sdcard_path.html

アンドロイドのSDカードパスの取得方法 ≪ 大阪のアンドロイド-iOS・ Webアプリ開発会社 ノーティス
http://www.notice.co.jp/archives/3074


とりあえず、[New Project]で(1ページ目)[Application name]:「SDCard」、[Company domain]:[example.com]、[Project location]:「C:\Documents and Settings\<User名>\AndroidStudioProjects\<Application name>」と設定(<Application name>は[Application name]で設定した名前と全く同じにして下さい)、(2ページ目)[Phone and Tablet][Minimum SDK]:[API 8:Android 2.2]と設定、(3ページ目)(「Activity」とかもテキストで上書きしてしまうので)「Activity」の設定は とりあえず「Empty Activity」としておく。

『<Project名>→app→main→res→drawable-hdpi』フォルダー(存在しない場合は作成してやる)内に「Android SDK」のデフォルト・アイコン「ic_menu_moreoverflow_normal_holo_light.png」をコピーする。
一応、「アプリの Logo マーク」を Android のマスコット・キャラの「Droid君」にしている。
下の方に『<Project名>→app→main→res→drawable-hdpi』フォルダー用の画像ファイル「アプリの Logo マーク:「48x48」Pixel、その他"TextView"用アイコンをアップしてあります。
"drawable-mdpi"フォルダー用は手抜きして作ってないが、解像度が低い端末でも自動的に その端末の解像度に合うように縮小して表示される。
その場合、当然 画質は悪くなるので画質を綺麗にしたい場合は手抜きせずにチャント"drawable-mdpi"フォルダー用の画像ファイルを作成してやれば良い。
「Toolbar」の UI はスタンダードな「ActionBarActivity」のような感じになっている。
(合っているハズなのに)エラーが消えない場合は一旦 Project を閉じてから、ふたたび Project を開いてみて下さい。


『<Project名>→app→main→java→com.example.sdcard→MainActivity(MainActivity.java)』


package com.example.sdcard;

import android.os.Bundle;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.KeyEvent;
import android.os.Environment;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.Context;
import android.content.res.Resources;
import android.content.Intent;
import android.app.PendingIntent;
import android.app.Activity;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
// import android.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.util.Log;

import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity
        implements View.OnClickListener {
    static final String fsDebugCrest = " 001";
    static final String tag = "LogCat.Debug"; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = true; // false; //
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    DisplayMetrics oDisplayMetrics;
    public static int iPhysicalWidth, iPhysicalHeight;
    float vfDisplayMetricsDensity;

    MainActivity oMainApp;
    Context oBaseContext;
    Context oAppContext; // Application Context.
    Context oContext; // Activity Context.
    String vsToolbarTitle;
    Menu oMainMenu;

    String vsPackageName;
    String vsVersionName;
    int iVersionCode;

    String vsDefaultSDCardRoot;
    String vsExtensionSDCardRoot;

    TextView wtvMessage;
    static final int[] WIDGETS =  new int[]{
            R.id.textView1, R.id.textView2,
            R.id.button1, R.id.checkBox1, R.id.toggleButton1, R.id.radioButton1 };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (DEBUG) Log.i(tag, "Activity.onCreate( );");
        setContentView(R.layout.activity_main);
        oMainApp = this;
// System 共通の Context:別の Application とやりとりするとき。
        oBaseContext = getBaseContext();
// Application 固有の Context(Application ごとに Context が変化する):Application 共通のモノが対象。
        oAppContext = getApplicationContext();
// Activity 固有の Context(Activity ごとに Context が変化する):Activity に依存モノが対象。
        oContext = this;
        oDisplayMetrics = oAppContext.getResources( ).getDisplayMetrics( );
        vfDisplayMetricsDensity = oDisplayMetrics.density;
        iPhysicalWidth = oDisplayMetrics.widthPixels;
        iPhysicalHeight = oDisplayMetrics.heightPixels;
        SDCardOp.ScanSDCardRoot( );
        vsDefaultSDCardRoot = SDCardOp.vsDefaultSDCardRoot;
        vsExtensionSDCardRoot = SDCardOp.vsExtensionSDCardRoot;
        if (DEBUG) Log.i(tag, "Activity.onCreate( ) : " +
                "vfDisplayMetricsDensity=" + vfDisplayMetricsDensity + "; " +
                "iPhysicalWidth=" + iPhysicalWidth + "; " +
                "iPhysicalHeight=" + iPhysicalHeight + "; " +
                "vsDefaultSDCardRoot=" + vsDefaultSDCardRoot + "; " +
                "vsExtensionSDCardRoot=" + vsExtensionSDCardRoot + "; " +
                "");

        vsPackageName = getPackageName();
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(
                    getPackageName(), PackageManager.GET_META_DATA);
            // 「<Project名>→app→build.gradle」ファイル内の
            // 「versionName、versionCode」の設定値を取得。
            vsVersionName = packageInfo.versionName;
            iVersionCode = packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            e.printStackTrace();
        }

        wtvMessage = (TextView) findViewById(R.id.wtvMessage);
        wtvMessage.append("fsDebugCrest=" + fsDebugCrest + ";\n" +
                "PackageName=" + vsPackageName + ";\n" +
                "VersionName=" + vsVersionName + ";\n" +
                "RevisionNo=" + String.valueOf(iVersionCode) + ";\n" +
                "vfDisplayMetricsDensity=" + vfDisplayMetricsDensity + ";\n" +
                "iPhysicalWidth=" + iPhysicalWidth + ";\n" +
                "iPhysicalHeight=" + iPhysicalHeight + ";\n" +
                "vsDefaultSDCardRoot=" + vsDefaultSDCardRoot + ";\n" +
                "vsExtensionSDCardRoot=" + vsExtensionSDCardRoot + ";\n" +
                "");

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        ActionBar actionbar = getSupportActionBar();

        // actionbar.setIcon(R.drawable.ic_menu_info_details);
        actionbar.setLogo(R.drawable.x_logo_android022);
        // ↑ 実際は Logo マークはアプリ固有の Icon にすべきモノです。
        // アプリ固有の Logo マークと言われてもイメージが湧かない方は
        // ブラウザの Icon(Logo マーク)をイメージしてみて下さい。
        // 下記サイトに主要ブラウザの Icon(Logo マーク)が掲載されています。
        // http://freesoft-100.com/pasokon/browser.html
        // ちなみに Toolbar に表示する Icon サイズのスタンダードは
        // "drawable-mdpi"フォルダー用は「32x32」、
        // "drawable-hdpi"フォルダー用は「48x48」だと思われます。

        vsToolbarTitle = "  " + getString(R.string.app_name) +
                fsDebugCrest +
                " V" + vsVersionName + // Version.
                " R" + String.valueOf(iVersionCode); // Revision.
// ↑ Toolbar に Logo マークを表示させるとタイトルと Logo マークとの間に隙間がないので、
// タイトルの先頭に半角スペースを2つ入れている。
        actionbar.setTitle(vsToolbarTitle);

        for (int iWId : WIDGETS) {
            View wvw = findViewById(iWId);
            wvw.setOnClickListener(this);
        }
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        if (DEBUG) Log.i(tag, "Activity.onRestart( );");
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (DEBUG) Log.i(tag, "Activity.onStart( );");
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (DEBUG) Log.i(tag, "Activity.onResume( );");
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (DEBUG) Log.i(tag, "Activity.onPause( );");
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (DEBUG) Log.i(tag, "Activity.onStop( );");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (DEBUG) Log.i(tag, "Activity.onDestroy( );");
    }

    @Override
    public void onClick(View v) {
        // super.onClick(v);
        if(DEBUG) Log.i(tag,"Activity.onClick( );");
        switch(v.getId()) {
            case  R.id.textView1 :
                if (DEBUG) Log.i(tag, "textView1");

                break;
            case  R.id.textView2 :
                if (DEBUG) Log.i(tag, "textView2");

                break;
            case R.id.button1 :
                if (DEBUG) Log.i(tag, "button1");

                break;
            case R.id.checkBox1 :
                if (DEBUG) Log.i(tag, "checkBox1");

                break;
            case R.id.toggleButton1 :
                if (DEBUG) Log.i(tag, "toggleButton1");

                break;
            case R.id.radioButton1 :
                if (DEBUG) Log.i(tag, "radioButton1");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
                return;
        }
        return;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        super.onCreateOptionsMenu(menu);
        // アクションバー内で使用する為のメニューアイテムを実体化
        MenuInflater inflater = getMenuInflater();
        // MenuInflater inflater = getSupportMenuInflater();
        inflater.inflate(R.menu.main, menu);
        // 「R.menu.main」の「menu.main」は 恐らく「res」からの「フォルダー名+ファイル名」
        oMainMenu = menu;
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        super.onOptionsItemSelected(item);
        if(DEBUG) Log.i(tag,"Activity.onOptionsItemSelected( );");
        switch(item.getItemId()) {
            case R.id.menu_main_settings :
                if (DEBUG) Log.i(tag, "menu_main_settings");

                break;
            case R.id.menu_main_settings_item1 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item1");

                break;
            case R.id.menu_main_settings_item2 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item2");

                break;
            case R.id.menu_main_settings_item3 :
                if (DEBUG) Log.i(tag, "menu_main_settings_item3");

                break;
            default :
                if (DEBUG) Log.i(tag, "default");
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
                return false; // true; //
        }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
        return true; // false; //
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        super.dispatchKeyEvent(event);
        if (DEBUG) Log.i(tag, "Activity.dispatchKeyEvent( );");
        final int action = event.getAction();
        final int keyCode = event.getKeyCode();
        switch (keyCode) {
            case KeyEvent.KEYCODE_MENU:
                if (DEBUG) Log.i(tag, "KeyEvent.KEYCODE_MENU;");
                if (action == KeyEvent.ACTION_UP) {
                    if (DEBUG) Log.i(tag, "KeyEvent.ACTION_UP;");
                    if (oMainMenu != null) {
                        if (DEBUG) Log.i(tag, "oMainMenu.performIdentifierAction( );");
                        oMainMenu.performIdentifierAction(R.id.menu_main_settings, 0);
                    }
// 戻り値に true を返す事で Event が「消費された」事が通知され、
// 他の Event Listener の処理はキャンセルされます。
                    return true; // false; //
                }
                break;

            default:
                if (DEBUG) Log.i(tag, "KeyEvent:Default");

                break;
        }
// 戻り値に false を返す事で Event は「消費されてない」事が通知され、
// 他の Event Listener の処理は継続されます。
        return false; // true; //
    }

}



『<Project名>→app→main→java→com.example.sdcard→SDCardOp(SDCardOp.java)』


package com.example.sdcard;

import android.os.Build;
import android.os.Environment;
import android.util.Log;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;

import java.util.List;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class SDCardOp {
    static final String tag = MainActivity.tag; // フィルタリング用タグ。
    // ↑ 分かりやすければ どんな文字列でも良い。
    static final boolean DEBUG = MainActivity.DEBUG;
    // ↑ この"DEBUG"は予約語では無い、"Java"において"final"(定数)の場合は
    // 全部 大文字で記述するの事が推奨されているようだ。
    // 「DEBUG=true」に設定すれば「if(DEBUG)Log.i(〜)」によって"LogCat"に出力される事になる。

    // SDCard が見つからない場合は Null になる。
    public static String vsDefaultSDCardRoot;
    public static String vsExtensionSDCardRoot;

    public static void ScanSDCardRoot( ) {
        if(DEBUG)Log.i(tag, "SDCardOp.getExpansionSDCardRoot( );");

// SDカードのマウント先をゲットするメソッド
        vsDefaultSDCardRoot = null;
        vsExtensionSDCardRoot = null;
        Scanner scanner = null;
        File file;
        String path, sdcheck;
        Pattern p;
        Matcher m;
        try {
// システム設定ファイルにアクセス
            File vold_fstab = new File("/system/etc/vold.fstab");
// マウント情報を取得する
            scanner = new Scanner(new FileInputStream(vold_fstab));
// 一行ずつ読み込む
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                if(DEBUG)Log.i(tag, "SDCardOp.getExpansionSDCardRoot( ) : "+
                        "line="+line+"; "+
                        "");
// 〜_mountで始まる行
                p = Pattern.compile("^[a-zA-Z]+_mount");
                m = p.matcher(line);
// なぜか「line.matches("^[a-zA-Z]+_mount")」では正常にチェックできなかった。
                if ( m.find( ) ) {
// 半角スペースではなくタブで区切られている機種もあるらしいので修正して
// 半角スペース区切り3つめ(path)を取得
                    path = line.replaceAll("\t", " ").split(" ")[2];
                    if(DEBUG) Log.i(tag, "SDCardOp.getExpansionSDCardRoot( ) : "+
                            "path="+path+"; "+
                            "");
                    if ( isMounted(path) ){
                        sdcheck = path + "/.android_secure";
                        if (DEBUG) Log.i(tag, "SDCardOp.getExpansionSDCardRoot( ) : " +
                                "sdcheck=" + sdcheck + "; " +
                                "");
                        file = new File(sdcheck);
                        if ( file.exists( ) ) {
                            vsExtensionSDCardRoot = path;
                        }else{
                            vsDefaultSDCardRoot = path;
                        }
                    }
                }
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }
        return;
    }

// 引数に渡したpathがマウントされているかどうかチェックするメソッド
    public static boolean isMounted(String path) {
        boolean isMounted = false;
        Scanner scanner = null;
        try {
// マウントポイントを取得する
            File mounts = new File("/proc/mounts");
            scanner = new Scanner(new FileInputStream(mounts));
// マウントポイントに該当するパスがあるかチェックする
            while (scanner.hasNextLine()) {
                if (scanner.nextLine().contains(path)) {
// 該当するパスがあればマウントされているってこと
                    isMounted = true;    break;
                }
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            if (scanner != null) {
                scanner.close();
            }
        }
// マウント状態をreturn
        return isMounted;
    }

}



『<Project名>→app→main→res→drawable→border.xml』(ファイルを作成)
※「UI Widget」の枠として使用する。


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="#ffffff" />
    <padding android:top="3dp" android:bottom="3dp"
        android:left="7dp" android:right="7dp" />
    <stroke android:width="1px" android:color="#000000" />
    <corners android:radius="5dp" />
</shape>



『<Project名>→app→main→res→drawable→button_style.xml』(ファイルを作成)
※「TextView」をボタン・スタイルで表示させている。


<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/ButtonBackground"/>
            <corners android:radius="3dp" />
            <stroke android:width="2dp" android:color="@color/ButtonShadow" />
            <!-- 「stroke android:width=」は ずらす幅より少し大きな値にする。 -->
        </shape>
    </item>
    <item android:bottom="1.45dp" android:right="1.47dp">
        <shape android:shape="rectangle">
            <solid android:color="@color/ButtonBackground"/>
            <padding android:top="4.4dp" android:bottom="4.4dp"
                android:left="5dp" android:right="5dp" />
            <corners android:radius="3dp" />
        </shape>
    </item>
</layer-list>



『<Project名>→app→main→res→layout→activity_main.xml』


<?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"
    tools:context=".MainActivity"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/white"
    android:orientation="vertical">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="?attr/actionBarSize"
        android:background="@color/Background_Cornflowerblue" />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:layout_margin = "7dp"
        android:background="@drawable/border" >

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="top"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:id="@+id/wtvMessage" />

    </ScrollView>

<!-- ボタンは左右にムダなスペースがあるので、表示幅が Tight でボタンが表示できない場合、-->
<!-- TextView をボタン・スタイルで表示させれば良い。 -->
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:textColor="@color/black"
            android:layout_marginTop = "6dp"
            android:layout_marginBottom = "6dp"
            android:layout_marginLeft = "3.5dp"
            android:layout_marginRight = "3.5dp"
            android:background="@drawable/button_style"
            android:text="TextView1"
            android:id="@+id/textView1" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:textColor="@color/black"
            android:layout_marginTop = "6dp"
            android:layout_marginBottom = "6dp"
            android:layout_marginLeft = "3.5dp"
            android:layout_marginRight = "3.5dp"
            android:background="@drawable/button_style"
            android:drawableRight="@drawable/xic_menu_moreoverflow_focused_holo_light0272"
            android:text="TextView2"
            android:id="@+id/textView2" />
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:gravity="center">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:text="Button1"
            android:id="@+id/button1" />

        <ToggleButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="22dp"
            android:text="ToggleButton1"
            android:id="@+id/toggleButton1" />
    </LinearLayout>

    <CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="22dp"
        android:text="CheckBox1"
        android:id="@+id/checkBox1" />

    <RadioButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="22dp"
        android:layout_gravity="center_horizontal"
        android:text="RadioButton1"
        android:id="@+id/radioButton1" />

</LinearLayout>



『<Project名>→app→src→main→res→menu→main.xml』(フォルダーとファイルを作成)


<?xml version="1.0" encoding="utf-8"?>
<menu
    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"
    tools:context=".MainActivity">

    <item android:id="@+id/menu_main_settings"
        android:title="Settings"
        android:icon="@drawable/ic_menu_moreoverflow_normal_holo_light"
        app:showAsAction="always">
        <!-- ↑ showAsAction:Toolbar に対する表示設定。 -->
        <!-- "always":常に表示、"never":常に表示しない、"ifRoom":表示する余裕があれば表示。 -->
        <menu>
            <item android:id="@+id/menu_main_settings_item1"
                android:title="item1">
            </item>

            <item android:id="@+id/menu_main_settings_item2"
                android:title="item2">
            </item>

            <item android:id="@+id/menu_main_settings_item3"
                android:title="item3">
            </item>
        </menu>
    </item>

</menu>



『<Project名>→app→src→main→res→values→colors.xml』


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#3F51B5</color>
    <color name="colorPrimaryDark">#303F9F</color>
    <color name="colorAccent">#FF4081</color>

    <color name="holo_blue_light">#33b5e5</color>
    <color name="Background_Cornflowerblue">#6495ED</color>
    <color name="Background_MediumslateblueLike">#9b7fff</color>

    <color name="ButtonBackground">#d7d7d7</color>
    <color name="ButtonShadow">#c6c6c6</color>
    <color name="GrayOutText001">#909090</color>
    <color name="GrayOutText002">#888888</color>
    <color name="GrayOutText003">#777777</color>
    <color name="SettingMenuBackground">#eeeeee</color>

    <color name="black">#000000</color>
    <color name="gray">#808080</color>
    <color name="silver">#c0c0c0</color>
    <color name="white">#ffffff</color>
    <color name="maroon">#800000</color>
    <color name="red">#ff0000</color>
    <color name="purple">#800080</color>
    <color name="fuchsia">#ff00ff</color>
    <color name="magenta">#ff00ff</color>
    <color name="green">#008000</color>
    <color name="lime">#00ff00</color>
    <color name="olive">#808000</color>
    <color name="yellow">#ffff00</color>
    <color name="navy">#000080</color>
    <color name="blue">#0000ff</color>
    <color name="teal">#008080</color>
    <color name="aqua">#00ffff</color>
    <color name="cyan">#00ffff</color>

    <color name="pink">#ffc0cb</color>
    <color name="orange">#ffa500</color>
    <color name="greenyellow">#adff2f</color>
    <color name="yellowgreen">#9acd32</color>
</resources>



『<Project名>→app→src→main→res→values→styles.xml』


<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>



『<Project名>→app→main→AndroidManifest.xml』


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

    <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:launchMode="singleInstance" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Block( Address 00000A54 Identity 0000090D )










ページの表示順:{ 新しい順/ 古い順}.
初期・ページの表示・位置:{ 先頭ページ/ 末尾ページ}.
1ページ内のスレッド表示数:

   
   

管理者用 Password:

  




SMT Version 8.022(+A) Release M6.
Author : amanojaku.


- Rental Orbit Space -