Android 電源ボタンを押すとSurfaceViewがおかしくなる

Android
Android ビデオ録画機能を作る

Android 電源ボタンを押すとSurfaceViewがおかしくなる

Androidのビデオ機能作成に取り組んでいる私ですが(Android ビデオを録画するサンプルコード)、またまた問題が発生しました…。
電源ボタンを押すと、SurfaceViewで表示する録画のプレビュー画面が電源ボタンを押したままの状態で固まってしまうんです。

理由を調べると、なんと電源ボタンを押して、また電源ボタンを押して戻った時は、

 surfaceCreated

が呼ばれない、そして

 surfaceChanged

が呼ばれる順番が違うのでした…。(ちなみに、ある程度の時間放置してスリープになる場合もこれと同様です。)
しかも、これはなんとAndroidの機種によって違うようです!!

  • Galaxys3aではこう
 //電源ボタンを押したとき
 05-26 11:08:43.017: I/MenuActivity(17260): onPause
 05-26 11:08:43.708: I/MenuActivity(17260): onStop
  
 //電源ボタンを押して復帰
 06-24 15:01:30.556: I/MenuActivity(13091): surfaceChanged
 06-24 15:01:30.906: I/MenuActivity(13091): onResume()
 06-24 15:01:31.631: I/MenuActivity(13091): surfaceChanged
  • Xperia SO 04Dではこう
 //電源ボタンを押したとき
 05-26 11:08:43.017: I/MenuActivity(17260): onPause
 05-26 11:08:43.708: I/MenuActivity(17260): onStop
  
 //電源ボタンを押して復帰
 06-24 15:01:30.906: I/MenuActivity(13091): onResume()

普通に最初にアプリ立ち上がり時はこう

 //最初に画面を作る時
 05-26 11:04:30.439: I/MenuActivity(17260): onCreate
 05-26 11:04:51.101: I/MenuActivity(17260): onCreate
 05-26 11:04:51.201: I/MenuActivity(17260): onResume
 05-26 11:04:51.531: I/MenuActivity(17260): surfaceCreated
 05-26 11:04:51.531: I/MenuActivity(17260): surfaceChanged

ちなみに、ホームボタンをタップした時はこう

 //ホームボタン
 05-26 11:09:34.472: I/MenuActivity(17260): onPause
 05-26 11:09:35.073: I/MenuActivity(17260): surfaceDestroyed
 05-26 11:09:35.823: I/MenuActivity(17260): onStop
  
 //ホームボタンから復帰
 05-26 11:09:39.007: I/MenuActivity(17260): onResume
 05-26 11:09:39.277: I/MenuActivity(17260): surfaceCreated
 05-26 11:09:39.277: I/MenuActivity(17260): surfaceChanged

Backボタンを押した時はこう

 //Backボタン
 05-26 11:11:26.822: I/MenuActivity(18100): onPause
 05-26 11:11:27.322: I/MenuActivity(18100): surfaceDestroyed
 05-26 11:11:27.743: I/MenuActivity(18100): onStop
 
 //Backから復帰
 05-26 11:12:08.326: I/MenuActivity(18100): onCreate
 05-26 11:12:08.436: I/MenuActivity(18100): onResume
 05-26 11:12:08.797: I/MenuActivity(18100): surfaceCreated
 05-26 11:12:08.797: I/MenuActivity(18100): surfaceChanged
 05-26 11:12:12.461: I/MenuActivity(18100): onPause

電源ボタンやスリープの場合だけ、surfaceCreatedが呼ばれないんですね(>_<) さて、解決方法というか、StackOverFlowさんによい例が書いてありましたので、紹介します。 http://stackoverflow.com/questions/11495842/how-surfaceholder-callbacks-are-related-to-activity-lifecycle

下手に使うと、カメラが落ちたりします。
原因は、大体

 camera.release();

がカメラ生成前に呼ばれていないことです。

とにかくフラグを使ってなんとかするのは面倒ですし、ソースコードもごちゃごちゃになってくるので避けたいですが、致し方ないですよね。

Good Luck!! [huh]

Android 録画中、最大サイズや時間を超えたらどうするか実装する

Android
Android ビデオ録画機能を作る

Android 録画中、最大サイズや時間を超えたらどうするか実装する

Androidでビデオ録画機能を作る時に気をつけたいこと
Android ビデオを録画するサンプルコード

などでビデオの録画方法について書いておりますが、録画中にありえることとして、たとえばユーザーがビデオを録画しっぱなしにしてしまったりすることもありえます。

その時に、スマホのストレージ以上をむやみに圧迫してしまってはいけないですよね。

MediaRecorderではビデオファイルの最大サイズや、録画時間の最長時間を決めておくことができます。

 if(mrec == null){
       mrec = new MediaRecorder(); 
       mrec.setOnInfoListener(this);
       mrec.setOnErrorListener(this);       
 }
 
 //サイズ
 long max_file_size = 5368709120L; //5G
 mrec.setMaxFileSize(max_file_size);
 
 mrec.setMaxDuration(600000); //10分

これで、5Gを超えた録画、あるいは10分を超えた録画は、次のリスナーで受け取れます。

 //OnInfoListener、OnErrorListenerのリスナーを実装しておく
 public class VideoActivity extends implements
        SurfaceHolder.Callback, 
        MediaRecorder.OnInfoListener, MediaRecorder.OnErrorListener{
 
 //中略
    @Override
    public void onInfo(MediaRecorder mr, int what, int extra) {
        
        Log.e(TAG, "インフォ:" + extra);
        
        String detail = null;
        if(what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED ){
            detail = "録画の最大時間を超えました";
        }else if (what ==  MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED ){
            detail = "録画の最大サイズを超えました";
        }
        
        showDialog(alertDialog, getString(R.string.record_warning), 
                detail +getString(R.string.please_tap_stop_and_restart) +" what:"+what + " extra:" + extra, HogeActivity.this);    
 
        stopVideoRecording();
    }
 
    @Override
    public void onError(MediaRecorder mr, int what, int extra) {
        
        Log.e(TAG, "エラー:" + extra);
        howDialog(alertDialog, getString(R.string.recording_error), 
                getString(R.string.please_tap_stop_and_restart) + "what:"+what + " extra:" + extra, HogeActivity.this);    
        stopVideoRecording();
     }
	

Android 証明書のフィンガープリントがMD5しか出てこない

Android

Android 証明書のフィンガープリントがMD5しか出てこない

Google Maps Android API v2などを利用するために、GoogleのAPIキーが必要ですが、SHAで暗号化されたものが必要になります。
Android Google Playで配布するアプリの証明書のフィンガープリントを取得などにあるように、フフンとフィンガープリントを取得しようとしますが、MD5のものしか表示されません。

 C:\Program Files\Java\jre7\bin>keytool -list -keystore "C:\Users\android_key"

「ちょ、待てよ!」
と思わずキムタクのように叫んでしまいます。

しかし、keytool の実行に -v オプションで詳細を表示するようにすると、SHAも表示されます。

 C:\Program Files\Java\jre7\bin>keytool -v -list -keystore "C:\Users\android_key"

よかった。

Android 複数選択のチェックリストのダイアログを作る

Android

Android 複数選択のチェックリストのダイアログを作る

意外と日本語情報がなかったので、書いておきます。
下記のようなダイアログを作ります。

;

     public class DaySelectDialogFragment extends DialogFragment {
 
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            mSelectedItems = new ArrayList(); // Where we track the selected
                                              // items
            boolean[] selected_days = new boolean[7];
            selected_days[0] = true; //最初からチェックしておきたい項目をtrueにする
            
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            // Set the dialog title
            builder.setTitle(R.string.day_of_week)
                    // Specify the list array, the items to be selected by
                    // default (null for none),
                    // and the listener through which to receive callbacks when
                    // items are selected
            
                    .setMultiChoiceItems(R.array.dayOfWeeklist, selected_days,
                            new DialogInterface.OnMultiChoiceClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which,
                                        boolean isChecked) {
                                    if (isChecked) {
                                        // If the user checked the item, add it
                                        // to the selected items
                                        mSelectedItems.add(which);
                                    } else if (mSelectedItems.contains(which)) {
                                        // Else, if the item is already in the
                                        // array, remove it
                                        mSelectedItems.remove(Integer.valueOf(which));
                                    }
                                }
                            })
                    // Set the action buttons
                    .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {
                            // User clicked OK, so save the mSelectedItems
                            // results somewhere
                            // or return them to the component that opened the
                            // dialog
                            //mSelectedItems = [0, 1, 2]
                            String type = "ON";
                            PutUtils.putAlarmDay(context, type, mSelectedItems);
                        }
                    })
                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {
 
                        }
                    });
 
            return builder.create();
        }
    }

OKをタップで

 mSelectedItems   

に選択した項目が、[0,1,2]という形で入ります。
んで、たとえばボタンをタップで上記のダイアログが開くようにします。

    @Override
    public void onClick(View v) {
        Intent i;
        switch (v.getId()) {
             
            case R.id.day_of_week:
                DaySelectDialogFragment day_select_dialog = new  DaySelectDialogFragment();
 
                day_select_dialog.show(m_fm_manager, TAG);
 
        }

忘れずに、onCreateでFragmentManagerを作っておきます。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.setting_alarm);
 
        btnAutoDay = (Button) findViewById(R.id.day_of_week);
        btnAutoDay.setOnClickListener(this);
 
        context = this;
 
        m_fm_manager = getSupportFragmentManager();
    }

下記のようなエラーが出てしまう場合は

 The method getSupportFragmentManager() is undefined for the type SampleActivity

ActivityがFragmentActivityを継承しているか確認しましょう。

一番最初のDialogFragmentは、ほぼ下記のページのコピペです。

http://developer.android.com/guide/topics/ui/dialogs.html

Android 疑似的にバッテリーレベルを変更する

Android

Androidアプリで、アプリ内でバッテリーレベルを検知して、何かする機能を作ってます。
しかし、充電ケーブルの抜き差しぐらいはいいとして、バッテリーが減ってきたとか、バッテリーがOKになってきたとかはデバッグするのが難しいですよね!!

そんな時にADBを使えば疑似的にAndroid端末のバッテリーが増えたり減ったりできる方法がありました。

下記のサイトさんを参考にさせて頂きましたm(_ _)m。
Android Shell. Part 1: Mocking Battery Status

ケーブルが取り外された

 adb shell dumpsys battery unplug

バッテリーのレベルを5にする

 adb shell dumpsys battery set level 5

などなどです。

注意しないといけないのは、

 adb shell dumpsys battery set level 0

ってすると、端末によっては電源がなくなったということで、再起動しちゃいます( ´∀` )。

また、デバッグし終わったら

 adb shell dumpsys battery reset

ってしないと、電池レベルを端末が誤解していてなんかおかしいことになります。