|
|
|
@ -16,12 +16,14 @@
|
|
|
|
|
|
|
|
|
|
package net.micode.notes.ui;
|
|
|
|
|
|
|
|
|
|
import android.Manifest;
|
|
|
|
|
import android.app.Activity;
|
|
|
|
|
import android.app.AlarmManager;
|
|
|
|
|
import android.app.AlertDialog;
|
|
|
|
|
import android.app.PendingIntent;
|
|
|
|
|
import android.app.SearchManager;
|
|
|
|
|
import android.appwidget.AppWidgetManager;
|
|
|
|
|
import android.content.ActivityNotFoundException;
|
|
|
|
|
import android.content.ContentResolver;
|
|
|
|
|
import android.content.ContentUris;
|
|
|
|
|
import android.content.ContentValues;
|
|
|
|
@ -29,24 +31,30 @@ import android.content.Context;
|
|
|
|
|
import android.content.DialogInterface;
|
|
|
|
|
import android.content.Intent;
|
|
|
|
|
import android.content.SharedPreferences;
|
|
|
|
|
import android.content.pm.PackageManager;
|
|
|
|
|
import android.database.Cursor;
|
|
|
|
|
import android.graphics.Bitmap;
|
|
|
|
|
import android.graphics.BitmapFactory;
|
|
|
|
|
import android.graphics.Paint;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import android.media.MediaPlayer;
|
|
|
|
|
import android.media.MediaRecorder;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import android.os.Build;
|
|
|
|
|
import android.os.Bundle;
|
|
|
|
|
import android.preference.PreferenceManager;
|
|
|
|
|
import android.provider.DocumentsContract;
|
|
|
|
|
import android.provider.MediaStore;
|
|
|
|
|
import android.speech.RecognizerIntent;
|
|
|
|
|
import android.text.Editable;
|
|
|
|
|
import android.text.Spannable;
|
|
|
|
|
import android.text.SpannableString;
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
import android.text.format.DateUtils;
|
|
|
|
|
import android.text.method.LinkMovementMethod;
|
|
|
|
|
import android.text.style.BackgroundColorSpan;
|
|
|
|
|
import android.text.style.ClickableSpan;
|
|
|
|
|
import android.text.style.ImageSpan;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
import android.view.LayoutInflater;
|
|
|
|
@ -82,8 +90,10 @@ import net.micode.notes.widget.NoteWidgetProvider_4x;
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.Locale;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
@ -200,10 +210,11 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
// initResources(); // 初始化资源
|
|
|
|
|
// }
|
|
|
|
|
private final int PHOTO_REQUEST=1;
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
|
|
|
super.onCreate(savedInstanceState);
|
|
|
|
|
|
|
|
|
|
this.setContentView(R.layout.note_edit);
|
|
|
|
|
|
|
|
|
|
if (savedInstanceState == null && !initActivityState(getIntent())) {
|
|
|
|
@ -228,6 +239,33 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
startActivityForResult(loadImage, PHOTO_REQUEST);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
// 根据id获取录音按钮
|
|
|
|
|
final ImageButton record_audio_btn = (ImageButton) findViewById(R.id.record_audio_btn);
|
|
|
|
|
record_audio_btn.setOnClickListener(new View.OnClickListener() {
|
|
|
|
|
private boolean isRecording = false;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
|
|
|
|
if (!isRecording) {
|
|
|
|
|
startRecordingAudio();
|
|
|
|
|
isRecording = true;
|
|
|
|
|
record_audio_btn.setImageResource(android.R.drawable.ic_media_pause); // 切换为暂停图标
|
|
|
|
|
} else {
|
|
|
|
|
stopRecordingAudio();
|
|
|
|
|
isRecording = false;
|
|
|
|
|
record_audio_btn.setImageResource(android.R.drawable.ic_btn_speak_now); // 切换为录音图标
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 根据id获取语音转文字按钮
|
|
|
|
|
final ImageButton speech_to_text_btn = (ImageButton) findViewById(R.id.speech_to_text_btn);
|
|
|
|
|
speech_to_text_btn.setOnClickListener(new View.OnClickListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View v) {
|
|
|
|
|
startSpeechToText();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 当活动被系统销毁后,为了恢复之前的状态,此方法会被调用。
|
|
|
|
@ -360,6 +398,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
|
|
|
|
|
// 获取图片路径并插入图片
|
|
|
|
|
String imagePath = mWorkingNote.getImagePathFromDatabase();
|
|
|
|
|
String audioPath = mWorkingNote.getAudioPathFromDatabase();
|
|
|
|
|
if (imagePath != null) {
|
|
|
|
|
// 从文件加载图像
|
|
|
|
|
Bitmap bitmap = mWorkingNote.loadImageFromFile(imagePath);
|
|
|
|
@ -376,11 +415,34 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Failed to load image from file: " + imagePath);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
Log.e(TAG, "Image path is null. Can't load image.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private MediaPlayer mediaPlayer;
|
|
|
|
|
private void playAudio(String path) {
|
|
|
|
|
if (mediaPlayer != null) {
|
|
|
|
|
mediaPlayer.release(); // 释放之前的实例
|
|
|
|
|
mediaPlayer = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mediaPlayer = new MediaPlayer();
|
|
|
|
|
try {
|
|
|
|
|
mediaPlayer.setDataSource(path); // 设置音频路径
|
|
|
|
|
mediaPlayer.prepare(); // 准备播放
|
|
|
|
|
mediaPlayer.start(); // 开始播放
|
|
|
|
|
|
|
|
|
|
// 设置播放完成的回调
|
|
|
|
|
mediaPlayer.setOnCompletionListener(mp -> {
|
|
|
|
|
mp.release(); // 播放完成时释放资源
|
|
|
|
|
mediaPlayer = null;
|
|
|
|
|
});
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
Toast.makeText(this, "Unable to play audio", Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 初始化笔记界面的函数。
|
|
|
|
@ -1292,7 +1354,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
// public boolean isDownloadsDocument(Uri uri) {
|
|
|
|
|
// return "com.android.providers.downloads.documents".equals(uri.getAuthority());
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
private static final int REQUEST_RECORD_AUDIO = 101;
|
|
|
|
|
private static final int REQUEST_SPEECH_INPUT = 102;
|
|
|
|
|
private String audioFilePath;
|
|
|
|
|
//是否为媒体文件
|
|
|
|
|
public boolean isMediaDocument(Uri uri) {
|
|
|
|
|
return "com.android.providers.media.documents".equals(uri.getAuthority());
|
|
|
|
@ -1327,6 +1391,31 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
Log.e(TAG, "Selected image URI is null."); // 输出错误日志
|
|
|
|
|
Toast.makeText(this, "No image selected.", Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
} // 音频录制处理逻辑
|
|
|
|
|
else if (requestCode == REQUEST_RECORD_AUDIO && resultCode == RESULT_OK && intent != null) {
|
|
|
|
|
Uri audioUri = intent.getData();
|
|
|
|
|
if (audioUri != null) {
|
|
|
|
|
String audioPath = getRealPathFromURI(audioUri);
|
|
|
|
|
mWorkingNote.saveAudioPathToDatabase(audioPath);
|
|
|
|
|
Toast.makeText(this, "Audio recorded successfully!", Toast.LENGTH_SHORT).show();
|
|
|
|
|
Log.d(TAG, "Audio inserted successfully: " + audioPath);
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Recorded audio URI is null.");
|
|
|
|
|
Toast.makeText(this, "Failed to record audio.", Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 语音转文字处理逻辑
|
|
|
|
|
else if (requestCode == REQUEST_SPEECH_INPUT && resultCode == RESULT_OK && intent != null) {
|
|
|
|
|
ArrayList<String> result = intent.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
|
|
|
|
|
if (result != null && !result.isEmpty()) {
|
|
|
|
|
String spokenText = result.get(0);
|
|
|
|
|
mNoteEditor.append(spokenText);
|
|
|
|
|
Toast.makeText(this, "Speech converted to text!", Toast.LENGTH_SHORT).show();
|
|
|
|
|
Log.d(TAG, "Speech to text: " + spokenText);
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Speech recognition result is null.");
|
|
|
|
|
Toast.makeText(this, "No speech recognized.", Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Log.e(TAG, "Activity result error, requestCode: " + requestCode + ", resultCode: " + resultCode);
|
|
|
|
|
}
|
|
|
|
@ -1359,4 +1448,85 @@ public class NoteEditActivity extends Activity implements OnClickListener,
|
|
|
|
|
noteEditText.getEditableText().insert(index, spannableString);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private MediaRecorder mediaRecorder;
|
|
|
|
|
|
|
|
|
|
private void startRecordingAudio() {
|
|
|
|
|
try {
|
|
|
|
|
audioFilePath = getExternalFilesDir(null).getAbsolutePath() + "/audio_" + System.currentTimeMillis() + ".3gp";
|
|
|
|
|
|
|
|
|
|
mediaRecorder = new MediaRecorder();
|
|
|
|
|
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
|
|
|
|
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
|
|
|
|
|
mediaRecorder.setOutputFile(audioFilePath);
|
|
|
|
|
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
|
|
|
|
|
|
|
|
|
|
mediaRecorder.prepare();
|
|
|
|
|
mediaRecorder.start();
|
|
|
|
|
|
|
|
|
|
Toast.makeText(this, "Recording started...", Toast.LENGTH_SHORT).show();
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
Toast.makeText(this, "Recording failed!", Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void stopRecordingAudio() {
|
|
|
|
|
if (mediaRecorder != null) {
|
|
|
|
|
mediaRecorder.stop();
|
|
|
|
|
mediaRecorder.release();
|
|
|
|
|
mediaRecorder = null;
|
|
|
|
|
|
|
|
|
|
// 获取音频路径
|
|
|
|
|
String audioPath = audioFilePath;
|
|
|
|
|
|
|
|
|
|
// 插入到光标位置
|
|
|
|
|
NoteEditText noteEditText = (NoteEditText) findViewById(R.id.note_edit_view);
|
|
|
|
|
int cursorPosition = noteEditText.getSelectionStart(); // 获取光标位置
|
|
|
|
|
SpannableString spannableString = new SpannableString("[Audio]");
|
|
|
|
|
spannableString.setSpan(new ImageSpan(this, R.drawable.ic_audio), 0, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
|
|
|
|
|
// 添加播放功能
|
|
|
|
|
spannableString.setSpan(new ClickableSpan() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClick(View widget) {
|
|
|
|
|
Log.d("AudioClick", "Audio icon clicked");
|
|
|
|
|
playAudio(audioPath); // 播放音频
|
|
|
|
|
}
|
|
|
|
|
}, 0, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
|
|
|
|
|
|
|
|
Editable editable = noteEditText.getEditableText();
|
|
|
|
|
editable.insert(cursorPosition, spannableString); // 插入到光标位置
|
|
|
|
|
|
|
|
|
|
// 保存音频路径到数据库
|
|
|
|
|
mWorkingNote.saveAudioPathToDatabase(audioPath);
|
|
|
|
|
mWorkingNote.saveNote();
|
|
|
|
|
// 确保编辑器启用点击事件
|
|
|
|
|
noteEditText.setMovementMethod(LinkMovementMethod.getInstance());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String getRealPathFromURI(Uri contentUri) {
|
|
|
|
|
Cursor cursor = getContentResolver().query(contentUri, null, null, null, null);
|
|
|
|
|
if (cursor == null) return contentUri.getPath();
|
|
|
|
|
cursor.moveToFirst();
|
|
|
|
|
int idx = cursor.getColumnIndex(MediaStore.Audio.AudioColumns.DATA);
|
|
|
|
|
String path = cursor.getString(idx);
|
|
|
|
|
cursor.close();
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void startSpeechToText() {
|
|
|
|
|
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
|
|
|
|
|
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
|
|
|
|
|
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
|
|
|
|
|
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speak now...");
|
|
|
|
|
try {
|
|
|
|
|
startActivityForResult(intent, REQUEST_SPEECH_INPUT);
|
|
|
|
|
} catch (ActivityNotFoundException e) {
|
|
|
|
|
Toast.makeText(this, "Speech to text not supported", Toast.LENGTH_SHORT).show();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|