diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3209b316..26300344 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ + + android:exported="false" + android:screenOrientation="landscape" /> enabled != 0)) { // TODO + XYLOG(XYLOG_SEVERITY_DEBUG, "Start init ncnn"); ncnn_init(); std::string paramFile = m_appPath + (APP_PATH_RECOG_PARAM); std::string binFile = m_appPath + (APP_PATH_RECOG_BIN); diff --git a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java index e4a845e7..43a034f2 100644 --- a/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java +++ b/app/src/main/java/com/xypower/mpapp/MicroPhotoService.java @@ -42,6 +42,7 @@ import android.os.SystemClock; import androidx.core.app.NotificationCompat; import androidx.core.content.FileProvider; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import android.telephony.SignalStrength; import android.telephony.SubscriptionManager; @@ -57,6 +58,7 @@ import com.dev.devapi.api.SysApi; import com.xypower.common.FileDownloader; import com.xypower.common.InetAddressUtils; import com.xypower.common.MicroPhotoContext; +import com.xypower.mpapp.video.VideoActivity; import java.io.File; import java.io.FileOutputStream; @@ -130,6 +132,7 @@ public class MicroPhotoService extends Service { protected long mNativeHandle = 0; private AlarmReceiver mAlarmReceiver = null; + private AlarmReceiver mLocalMsgReceiver = null; private ScreenActionReceiver mScreenaAtionReceiver = null; private NetworkChangedReceiver mNetworkChangedReceiver = null; @@ -165,11 +168,11 @@ public class MicroPhotoService extends Service { mAlarmReceiver = new AlarmReceiver(this); IntentFilter intentFilter = new IntentFilter(ACTION_HEARTBEAT); intentFilter.addAction(ACTION_TAKE_PHOTO); - intentFilter.addAction(ACTION_TAKE_PHOTO_MANUALLY); - intentFilter.addAction(ACTION_HEARTBEAT_MANUALLY); - intentFilter.addAction(ACTION_MSG_BROADCAST); - intentFilter.addAction(ACTION_VIDEO_FINISHED); - intentFilter.addAction(ACTION_STOP); + // intentFilter.addAction(ACTION_TAKE_PHOTO_MANUALLY); + // intentFilter.addAction(ACTION_HEARTBEAT_MANUALLY); + // intentFilter.addAction(ACTION_MSG_BROADCAST); + // intentFilter.addAction(ACTION_VIDEO_FINISHED); + // intentFilter.addAction(ACTION_STOP); // intentFilter.addCategory(Intent.CATEGORY_DEFAULT); getApplicationContext().registerReceiver(mAlarmReceiver, intentFilter, Context.RECEIVER_EXPORTED | Context.RECEIVER_VISIBLE_TO_INSTANT_APPS); @@ -178,6 +181,20 @@ public class MicroPhotoService extends Service { // registerRe } + { + mLocalMsgReceiver = new AlarmReceiver(this); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_HEARTBEAT); + intentFilter.addAction(ACTION_TAKE_PHOTO); + intentFilter.addAction(ACTION_TAKE_PHOTO_MANUALLY); + intentFilter.addAction(ACTION_HEARTBEAT_MANUALLY); + intentFilter.addAction(ACTION_MSG_BROADCAST); + intentFilter.addAction(ACTION_VIDEO_FINISHED); + intentFilter.addAction(ACTION_STOP); + + LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver (mLocalMsgReceiver, intentFilter); + } + { mNetworkChangedReceiver = new NetworkChangedReceiver(this); IntentFilter filter = new IntentFilter(); @@ -226,6 +243,8 @@ public class MicroPhotoService extends Service { getApplicationContext().unregisterReceiver(mScreenaAtionReceiver); getApplicationContext().unregisterReceiver(mNetworkChangedReceiver); + LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(mLocalMsgReceiver); + for(Map.Entry entry : mWakeLocks.entrySet()) { try { PowerManager.WakeLock wl = entry.getValue(); @@ -387,6 +406,8 @@ public class MicroPhotoService extends Service { } } } + + // Will be called fron native private void registerHeartbeatTimer(int duration) { int orgHeartbeatDuration = mHeartbeatDuration; mHeartbeatDuration = duration; @@ -412,36 +433,42 @@ public class MicroPhotoService extends Service { private static void registerPhotoTimer(Context context, long scheduleTime, long takingTime, long timeout, List schedules) { // 创建延迟意图 - Intent alarmIntent = new Intent(); - alarmIntent.setAction(ACTION_TAKE_PHOTO); + Intent intent = new Intent(); + intent.setAction(ACTION_TAKE_PHOTO); int cnt = schedules.size(); - alarmIntent.putExtra(EXTRA_PARAM_SCHEDULES, cnt); + intent.putExtra(EXTRA_PARAM_SCHEDULES, cnt); StringBuilder channelStr = new StringBuilder(); long val = 0; for (int idx = 0; idx < cnt; idx++) { val = schedules.get(idx).longValue(); - alarmIntent.putExtra(EXTRA_PARAM_SCHEDULE + idx, schedules.get(idx).longValue()); + intent.putExtra(EXTRA_PARAM_SCHEDULE + idx, schedules.get(idx).longValue()); channelStr.append("(" + ((val & 0XFF0000) >> 16) + "-" + Long.toString (((val & 0XFF00) >> 8), 16).toUpperCase() + ") "); } - alarmIntent.putExtra(EXTRA_PARAM_TIME, scheduleTime); - alarmIntent.putExtra(EXTRA_PARAM_TAKING_TIME, takingTime); + intent.putExtra(EXTRA_PARAM_TIME, scheduleTime); + intent.putExtra(EXTRA_PARAM_TAKING_TIME, takingTime); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); + if (timeout == 0) { + // LocalBroadcast + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); + localBroadcastManager.sendBroadcast(intent); + } else { + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); - try { - alarmManager.cancel(pendingIntent); - } catch (Exception ex) { - ex.printStackTrace(); - } + AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); + try { + alarmManager.cancel(pendingIntent); + } catch (Exception ex) { + ex.printStackTrace(); + } - long currentTimeMillis = System.currentTimeMillis(); - Date date = new Date(currentTimeMillis + timeout); - String dateStr = (String) DateFormat.format("MM-dd kk:mm:ss", date); - Log.d(TAG, "PhotoTimer Reg: " + dateStr + " currentTimeMillis=" + currentTimeMillis + " timeout=" + timeout + " CH-PR=" + channelStr.toString()); + long currentTimeMillis = System.currentTimeMillis(); + Date date = new Date(currentTimeMillis + timeout); + String dateStr = (String) DateFormat.format("MM-dd kk:mm:ss", date); + Log.d(TAG, "PhotoTimer Reg: " + dateStr + " currentTimeMillis=" + currentTimeMillis + " timeout=" + timeout + " CH-PR=" + channelStr.toString()); - alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent); + alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent); + } } private void registerPhotoTimer(long scheduleTime, long timeout, List schedules) { @@ -451,10 +478,8 @@ public class MicroPhotoService extends Service { public void startRecording(int cameraId, long videoId, int duration, int width, int height, int quality) { Context context = getApplicationContext(); - Intent intent = context.getPackageManager().getLaunchIntentForPackage("com.xypower.mvapp"); - if (intent == null) { - return; - } + Intent intent = new Intent(this, VideoActivity.class); + intent.putExtra("cameraId", cameraId); intent.putExtra("videoId", videoId); intent.putExtra("duration", duration); @@ -462,8 +487,8 @@ public class MicroPhotoService extends Service { intent.putExtra("height", height); intent.putExtra("quality", quality); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - context.startActivity(intent); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); } protected boolean updateCaptureSchedule(long startTime) { @@ -502,33 +527,15 @@ public class MicroPhotoService extends Service { schedules.add(Long.valueOf(val)); - registerPhotoTimer(context, 0, ts, 100, schedules); + registerPhotoTimer(context, 0, ts, 0, schedules); } public static void sendHeartbeat(Context context) { - - Intent alarmIntent = new Intent(); - - // if(Build.VERSION.SDK_INT >= 26) { - // alarmIntent.addFlags(0x01000000); - //} - // String className = AlarmReceiver.class.getName(); - // alarmIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - // context.startService(alarmIntent); - - // alarmIntent.setComponent(new ComponentName(context.getPackageName(), className)); - // alarmIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - // alarmIntent.setPackage(context.getPackageName()); - alarmIntent.setAction(ACTION_HEARTBEAT_MANUALLY); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0); - - // context.sendBroadcast(alarmIntent); - // context.sendBroadcast(alarmIntent); - // LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); - // localBroadcastManager.sendBroadcast(alarmIntent); - - AlarmManager alarmManager = (AlarmManager) context.getSystemService(ALARM_SERVICE); - alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 100, pendingIntent); + Intent intent = new Intent(); + intent.setAction(ACTION_HEARTBEAT_MANUALLY); + // PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0); + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); + localBroadcastManager.sendBroadcast(intent); } @Override diff --git a/app/src/main/java/com/xypower/mpapp/video/VideoActivity.java b/app/src/main/java/com/xypower/mpapp/video/VideoActivity.java index 328aefce..19139346 100644 --- a/app/src/main/java/com/xypower/mpapp/video/VideoActivity.java +++ b/app/src/main/java/com/xypower/mpapp/video/VideoActivity.java @@ -1,7 +1,9 @@ package com.xypower.mpapp.video; import androidx.appcompat.app.AppCompatActivity; +import androidx.fragment.app.Fragment; +import android.content.Intent; import android.os.Bundle; import com.xypower.mpapp.R; @@ -14,8 +16,30 @@ public class VideoActivity extends AppCompatActivity { setContentView(R.layout.activity_video); if (null == savedInstanceState) { + Bundle bundle = new Bundle(); + Intent intent = getIntent(); + + int duration = intent.getIntExtra("duration", 0); + if (intent.hasExtra("cameraId")) { + bundle.putInt("cameraId", intent.getIntExtra("cameraId", 0)); + } + if (intent.hasExtra("videoId")) { + bundle.putLong("videoId", intent.getLongExtra("videoId", 0)); + } + + bundle.putInt("duration", duration); + String act = intent.getStringExtra("action"); + if (act != null) { + bundle.putString("action", act); + } + + bundle.putInt("width", intent.getIntExtra("width", 0)); + bundle.putInt("height", intent.getIntExtra("height", 0)); + + Fragment fragment = VideoFragment.newInstance(); + fragment.setArguments(bundle); getSupportFragmentManager().beginTransaction() - .replace(R.id.container, VideoFragment.newInstance()) + .replace(R.id.container, fragment) .commit(); } } diff --git a/app/src/main/java/com/xypower/mpapp/video/VideoFragment.java b/app/src/main/java/com/xypower/mpapp/video/VideoFragment.java index edbef909..0f62297b 100644 --- a/app/src/main/java/com/xypower/mpapp/video/VideoFragment.java +++ b/app/src/main/java/com/xypower/mpapp/video/VideoFragment.java @@ -3,8 +3,10 @@ package com.xypower.mpapp.video; import android.Manifest; import android.app.Activity; import android.app.Dialog; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Matrix; @@ -28,7 +30,9 @@ import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.legacy.app.FragmentCompat; import androidx.legacy.app.FragmentCompat; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import android.util.Log; @@ -42,6 +46,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.Toast; +import com.xypower.mpapp.MicroPhotoService; import com.xypower.mpapp.R; import java.io.File; @@ -58,7 +63,10 @@ import java.util.concurrent.TimeUnit; * Use the {@link VideoFragment#newInstance} factory method to * create an instance of this fragment. */ -public class VideoFragment extends Fragment implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback { +public class VideoFragment extends Fragment implements View.OnClickListener, MediaRecorder.OnInfoListener, FragmentCompat.OnRequestPermissionsResultCallback { + + public static final String ACTION_FINISH = "com.xypower.mvapp.ACT_FINISH"; + public static final String ACTION_MP_VIDEO_FINISHED = "com.xypower.mpapp.ACT_V_FINISHED"; private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; @@ -88,6 +96,10 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0); } + private String mCameraId; + private long mVideoId = 0; + private int mDuration = 0; + /** * An {@link AutoFitTextureView} for camera preview. */ @@ -169,6 +181,8 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra */ private Handler mBackgroundHandler; + private Handler mMainHandler; + /** * A {@link Semaphore} to prevent the app from exiting before closing the camera. */ @@ -212,6 +226,49 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra private String mNextVideoAbsolutePath; private CaptureRequest.Builder mPreviewBuilder; + public void onInfo(MediaRecorder mr, int what, int extra) { + if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) { + Log.v(TAG, "Recording Maximum Duration Reached"); + final Runnable quitRunnable = new Runnable() { + @Override + public void run() { + getActivity().finish(); + } + }; + + if (mIsRecordingVideo) { + stopRecordingVideo(); + Log.i(TAG, "Stop recording"); + + mMainHandler.post(quitRunnable); + } + } + } + + private void broadcastVideoFile(boolean result, String path) { + if (mDuration <= 0) { + return; + } + + Context context = getContext(); + String receiverName = MicroPhotoService.AlarmReceiver.class.getName(); + String packageName = context.getPackageName(); + + Intent intent = new Intent(ACTION_MP_VIDEO_FINISHED); + // intent.setPackage(packageName); + intent.putExtra("result", result); + intent.putExtra("path", path); + intent.putExtra("videoId", mVideoId); + + // intent.setComponent(new ComponentName(packageName, receiverName)); + + Log.i(TAG, "Notify recording videoId=" + Long.toString(mVideoId) + " " + path); + LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(getContext().getApplicationContext()); + localBroadcastManager.sendBroadcast(intent); + + context.sendBroadcast(intent); + } + public static Fragment newInstance() { return new VideoFragment(); } @@ -277,12 +334,37 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra mButtonVideo = (Button) view.findViewById(R.id.video); mButtonVideo.setOnClickListener(this); view.findViewById(R.id.info).setOnClickListener(this); + + mMainHandler = new Handler(); + + Bundle argument = getArguments(); + if (argument != null) { + mCameraId = Integer.toString(argument.getInt("CameraId", 0)); + mVideoId = argument.getLong("videoId", 0); + mDuration = argument.getInt("duration", 0); + } + + Log.i(TAG, "Recv recording request CameraId=" + mCameraId + " videoId=" + Long.toString(mVideoId)); } @Override public void onResume() { super.onResume(); startBackgroundThread(); + Runnable runnable = new Runnable() { + @Override + public void run() { + if (!mIsRecordingVideo) { + Log.i(TAG, "Start recording duration=" + Integer.toString(mDuration)); + startRecordingVideo(); + } + } + }; + + if (mDuration > 0) { + mMainHandler.postDelayed(runnable, 1000); + } + if (mTextureView.isAvailable()) { openCamera(mTextureView.getWidth(), mTextureView.getHeight()); } else { @@ -414,9 +496,10 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra } configureTransform(width, height); mMediaRecorder = new MediaRecorder(); + mMediaRecorder.setOnInfoListener(this); manager.openCamera(cameraId, mStateCallback, null); } catch (CameraAccessException e) { - Toast.makeText(activity, "Cannot access the camera.", Toast.LENGTH_SHORT).show(); + // Toast.makeText(activity, "Cannot access the camera.", Toast.LENGTH_SHORT).show(); activity.finish(); } catch (NullPointerException e) { // Currently an NPE is thrown when the Camera2API is used but not supported on the @@ -477,7 +560,8 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra public void onConfigureFailed(@NonNull CameraCaptureSession session) { Activity activity = getActivity(); if (null != activity) { - Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show(); + broadcastVideoFile(false, ""); + // Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show(); } } }, mBackgroundHandler); @@ -543,7 +627,12 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra if (null == activity) { return; } - mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + try { + // mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + } catch (Exception ex) { + + } mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); if (mNextVideoAbsolutePath == null || mNextVideoAbsolutePath.isEmpty()) { @@ -555,6 +644,9 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight()); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); + if (mDuration > 0) { + mMediaRecorder.setMaxDuration(mDuration * 1000); + } int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); switch (mSensorOrientation) { case SENSOR_ORIENTATION_DEFAULT_DEGREES: @@ -568,9 +660,18 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra } private String getVideoFilePath(Context context) { - final File dir = context.getExternalFilesDir(null); - return (dir == null ? "" : (dir.getAbsolutePath() + "/")) - + System.currentTimeMillis() + ".mp4"; + // final File dir = context.getExternalFilesDir(null); + String path = Environment.getExternalStorageDirectory().getAbsolutePath(); + if (!path.endsWith(File.separator)) { + path += File.separator; + } + path += context.getPackageName() + File.separator; + File file = new File(path); + if (!file.exists()) { + file.mkdirs(); + } + path += System.currentTimeMillis() + ".mp4"; + return path; } private void startRecordingVideo() { @@ -621,7 +722,8 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { Activity activity = getActivity(); if (null != activity) { - Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show(); + broadcastVideoFile(false, ""); + // Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show(); } } }, mBackgroundHandler); @@ -648,9 +750,10 @@ public class VideoFragment extends Fragment implements View.OnClickListener, Fra Activity activity = getActivity(); if (null != activity) { - Toast.makeText(activity, "Video saved: " + mNextVideoAbsolutePath, - Toast.LENGTH_SHORT).show(); - Log.d(TAG, "Video saved: " + mNextVideoAbsolutePath); + // Toast.makeText(activity, "Video saved: " + mNextVideoAbsolutePath, Toast.LENGTH_SHORT).show(); + // Log.d(TAG, "Video saved: " + mNextVideoAbsolutePath); + + broadcastVideoFile(true, mNextVideoAbsolutePath); } mNextVideoAbsolutePath = null; startPreview();