|
<Number>: [00000A23]
<Date>: 2016/04/27 18:15:52
<Title>:
<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>:
<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>:
<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>:
<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>
|
|
|