package com.xinyingpower.microphoto; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Environment; import android.os.IBinder; import android.os.SystemClock; import androidx.core.app.NotificationCompat; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.Log; import android.widget.RemoteViews; import android.widget.Toast; import com.dowse.camera.client.DSCameraManager; import java.io.File; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; public class MicroPhotoService extends Service { public static final String TAG = "MPService"; // Used to load the 'microphoto' library on application startup. static { System.loadLibrary("microphoto"); } private static String ALARM_EVENT = "com.xinyingpower.mp.MicroPhotoService.AlarmReceiver"; public static final int NOTIFICATION_ID_FOREGROUND_SERVICE = 8466503; public static final String ACTION_START = "ACT_START"; public static final String ACTION_STOP = "ACT_STOP"; public static final String ACTION_MAIN = "ACT_MAIN"; private static String ACTION_HEARTBEAT = "ACT_HB"; private static String ACTION_TAKE_PHOTO = "ACT_TP"; private static String ACTION_TAKE_PHOTO_MANUALLY = "ACT_TP_M"; private static String ACTION_TIMEOUT = "ACT_TIMEOUT"; private static String EXTRA_PARAM_CHANNEL = "Channel"; private static String EXTRA_PARAM_PRESET = "Preset"; private static String EXTRA_PARAM_PHOTO_OR_VIDEO = "PhotoOrVideo"; private static String EXTRA_PARAM_SCHEDULES = "Schedules"; private static String EXTRA_PARAM_SCHEDULE = "Schedule_"; private static String EXTRA_PARAM_TIME = "Time"; // private static String EXTRA_PARAM_FILENAME = "FileName"; private static String EXTRA_PARAM_TIMER_UID = "TimerUid"; // private static String EXTRA_PARAM_TIMER_TYPE = "TimerType"; private static String EXTRA_PARAM_TIMEOUT = "Timeout"; private final static String FOREGROUND_CHANNEL_ID = "foreground_channel_id"; public static class STATE_SERVICE { public static final int CONNECTED = 10; public static final int NOT_CONNECTED = 0; } private AlarmManager mAlarmManager; private NotificationManager mNotificationManager; private int mHeartbeatDuration = 0; // 5m: 5 * 60 * 1000 private long mNextHeartbeatTime = 0; private Map mTimers = new HashMap<>(); private static int stateService = STATE_SERVICE.NOT_CONNECTED; public MicroPhotoService() { } @Override public IBinder onBind(Intent intent) { // TODO: Return the communication channel to the service. throw new UnsupportedOperationException("Not yet implemented"); } @Override public void onCreate() { super.onCreate(); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); stateService = STATE_SERVICE.NOT_CONNECTED; alarmReceiver = new AlarmReceiver(this); // 注册广播接受者 IntentFilter intentFilter = new IntentFilter(ACTION_HEARTBEAT); intentFilter.addAction(ACTION_TAKE_PHOTO); intentFilter.addAction(ACTION_TIMEOUT); intentFilter.addAction(ACTION_TAKE_PHOTO_MANUALLY); registerReceiver( alarmReceiver, intentFilter); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); while (true) { AlarmManager.AlarmClockInfo aci = alarmManager.getNextAlarmClock(); if (aci == null) { break; } if (aci.getShowIntent().isBroadcast()) { // alarmManager.cancel(aci.getShowIntent()); } } // alarmManager.cancel(); boolean res = false; /* res = DSCameraManager.getInstace().init(); ChannelPicParam picParam = new ChannelPicParam(); picParam.setColor(1); picParam.setWidth(1920); picParam.setHeight(1080); picParam.setCompress_radio(40); DSCameraManager.getInstace().setPicParam(1, picParam); */ // Environment.getExternalStoragePublicDirectory(String) File path = new File(Environment.getExternalStorageDirectory(), "com.xyp.mp/"); if (!path.exists()) { path.mkdirs(); } // File path = getApplicationContext().getFilesDir(); String appPath = path.getAbsolutePath(); Log.i(TAG, "AppPath=" + appPath); String ip = "180.166.218.222"; int port = 40032; String cmdid = "XYDEV100230100012"; init(appPath, ip, port, cmdid); // registerHeartbeatTimer(getHeartbeatDuration()); Date date = new Date(); long nowTs = date.getTime() / 1000; date.setHours(0); date.setMinutes(0); date.setSeconds(0); long startTime = date.getTime() / 1000; long baseTime = nowTs - startTime; registerCaptureSchedule(startTime, baseTime); // registerPhotoTimer(); } public static class AlarmReceiver extends BroadcastReceiver { private MicroPhotoService mService; public AlarmReceiver(MicroPhotoService service) { mService = service; } public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if(TextUtils.equals(ACTION_HEARTBEAT, action)){ Log.i(TAG, "receiver ACTION=" + action); mService.sendHeartbeat(); mService.registerHeartbeatTimer(); } else if(TextUtils.equals(ACTION_TAKE_PHOTO, action)) { long ts = intent.getLongExtra(EXTRA_PARAM_TIME, 0); int cnt = intent.getIntExtra(EXTRA_PARAM_SCHEDULES, 0); if (cnt > 0) { for (int idx = 0; idx < cnt; idx++) { long val = intent.getLongExtra(EXTRA_PARAM_SCHEDULE + idx, 0); int channel = (int)((val & 0xFF0000L) >> 16); int preset = (int)((val & 0xFF00L) >> 8); mService.notifyToTakePhoto(channel, preset, ts, mService.buildPhotoDir(channel), mService.buildPhotoFileName(channel, preset, ts), true); } } // Register Next Photo Timer Date date = new Date(); long nowTs = date.getTime() / 1000; date.setHours(0); date.setMinutes(0); date.setSeconds(0); long startTime = date.getTime() / 1000; long baseTime = nowTs - startTime; mService.registerCaptureSchedule(startTime, baseTime); } else if(TextUtils.equals(ACTION_TAKE_PHOTO_MANUALLY, action)) { int channel = intent.getIntExtra(EXTRA_PARAM_CHANNEL, 0); int preset = intent.getIntExtra(EXTRA_PARAM_PRESET, 0); // long ts = intent.getLongExtra(EXTRA_PARAM_TIME, 0); boolean photoOrVideo = intent.getBooleanExtra(EXTRA_PARAM_PHOTO_OR_VIDEO, true); long ts = System.currentTimeMillis() / 1000; mService.notifyToTakePhoto(channel, preset, ts, mService.buildPhotoDir(channel), mService.buildPhotoFileName(channel, preset, ts), photoOrVideo); } else if(TextUtils.equals(ACTION_TIMEOUT, action)) { long uid = intent.getLongExtra(EXTRA_PARAM_TIMER_UID, 0); Log.i(TAG, "Timeout:" + uid); mService.fireTimeout(uid); int timeout = intent.getIntExtra(EXTRA_PARAM_TIMEOUT, 0); Long uidObj = Long.valueOf(uid); PendingIntent pendingIntent = mService.mTimers.get(uidObj); if (pendingIntent != null) { mService.registerTimer(pendingIntent, uid, timeout); } } } } private void registerHeartbeatTimer(int duration) { int orgHeartbeatDuration = mHeartbeatDuration; mHeartbeatDuration = duration; if (orgHeartbeatDuration == 0) { registerHeartbeatTimer(); } } private void registerHeartbeatTimer() { // 创建延迟意图 Intent alarmIntent = new Intent(); alarmIntent.setAction(ACTION_HEARTBEAT); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + mHeartbeatDuration, pendingIntent); mNextHeartbeatTime = System.currentTimeMillis() + mHeartbeatDuration; // alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + timeout, pendingIntent); } private void registerPhotoTimer(int channel, int preset, long ts, long timeout, List schedules) { // 创建延迟意图 Intent alarmIntent = new Intent(); alarmIntent.setAction(ACTION_TAKE_PHOTO); int cnt = schedules.size(); alarmIntent.putExtra(EXTRA_PARAM_SCHEDULES, cnt); String channelStr = ""; for (int idx = 0; idx < cnt; idx++) { alarmIntent.putExtra(EXTRA_PARAM_SCHEDULE + idx, schedules.get(idx).longValue()); channelStr += schedules.get(idx).toString() + " "; } alarmIntent.putExtra(EXTRA_PARAM_TIME, ts); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); long currentTimeMillis = System.currentTimeMillis(); Date date = new Date(currentTimeMillis + timeout); String dateStr = (String) DateFormat.format("MM-dd kk:mm:ss", date); Log.d(TAG, "Register Photo Timer: " + dateStr + " currentTimeMillis=" + currentTimeMillis + " timeout=" + timeout + " Channels=" + channelStr); alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent); } // private HashMap mTimers = new HashMap(); public boolean registerTimer(long uid, int timeout) { // 创建延迟意图 Intent alarmIntent = new Intent(); alarmIntent.setAction(ACTION_TIMEOUT); alarmIntent.putExtra(EXTRA_PARAM_TIMER_UID, uid); alarmIntent.putExtra(EXTRA_PARAM_TIMEOUT, timeout); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); mTimers.put(Long.valueOf(uid), pendingIntent); return registerTimer(pendingIntent, uid, timeout); } public boolean registerTimer(PendingIntent pendingIntent, long uid, int timeout) { AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + timeout, pendingIntent); Log.i(TAG, "RegTimer:" + uid + " timeout=" + timeout); return true; } public boolean unregisterTimer(long uid) { Long uidObj = Long.valueOf(uid); PendingIntent pendingIntent = mTimers.get(uidObj); if (pendingIntent != null) { AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); alarmManager.cancel(pendingIntent); mTimers.remove(uidObj); Log.i(TAG, "UnregTimer:" + uid); } return true; } private boolean registerCaptureSchedule(long startTime, long baseTime) { long[] photoTimeData = getPhotoTimeData(); if (photoTimeData == null) { return false; } int cnt = photoTimeData.length; short channel = 0; short preset = 0; int ts = 0; long val = 0L; // int maxDuration = mHeartbeatDuration * 2 / 1000; int maxDuration = 35 * 60 * 1000 + 1000; int currentTs = 0; List schedules = new ArrayList<>(); for (int idx = 0; idx < cnt; idx++) { val = photoTimeData[idx]; ts = (int)((val & 0x00FFFFFF00000000L) >> 32); if (ts < baseTime) { continue; } if ((ts - baseTime) > maxDuration) { break; } if (currentTs == 0) { currentTs = ts; channel = (short)((val & 0xFF0000L) >> 16); preset = (short)((val & 0xFF00L) >> 8); schedules.add(Long.valueOf(val)); } else if (ts > currentTs) { break; } else { schedules.add(Long.valueOf(val)); } } if (!schedules.isEmpty()) { long expectedTs = startTime + ts; Date date = new Date(expectedTs * 1000); // Log.d(TAG, "Register Photo Time: " + date.toString() + " BaseTime=" + baseTime + " ts=" + ts + " startTime=" + startTime + " expectedTs=" + expectedTs); registerPhotoTimer(channel, preset, currentTs, (currentTs - baseTime) * 1000, schedules); } return true; } public static void takePhoto(Context context, int channel, int preset, boolean photoOrVideo) { Intent intent = new Intent(ACTION_TAKE_PHOTO_MANUALLY); intent.putExtra(EXTRA_PARAM_CHANNEL, channel); intent.putExtra(EXTRA_PARAM_PRESET, preset); intent.putExtra(EXTRA_PARAM_PHOTO_OR_VIDEO, photoOrVideo); context.sendBroadcast(intent); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent == null) { stopForeground(true); stopSelf(); return START_NOT_STICKY; } // if user starts the service switch (intent.getAction()) { case ACTION_START: Log.d(TAG, "Received user starts foreground intent"); startForeground(NOTIFICATION_ID_FOREGROUND_SERVICE, prepareNotification()); // Start the locker receiver final ScreenActionReceiver screenactionreceiver = new ScreenActionReceiver(); registerReceiver(screenactionreceiver, screenactionreceiver.getFilter()); connect(); break; case ACTION_STOP: stopForeground(true); stopSelf(); break; default: stopForeground(true); stopSelf(); } return START_NOT_STICKY; } private void connect() { // after 10 seconds its connected new android.os.Handler().postDelayed( new Runnable() { public void run() { Log.d(TAG, "Bluetooth Low Energy device is connected!!"); Toast.makeText(getApplicationContext(),"Connected!",Toast.LENGTH_SHORT).show(); stateService = STATE_SERVICE.CONNECTED; startForeground(NOTIFICATION_ID_FOREGROUND_SERVICE, prepareNotification()); } }, 10000); } private Notification prepareNotification() { // handle build version above android oreo if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O && mNotificationManager.getNotificationChannel(FOREGROUND_CHANNEL_ID) == null) { CharSequence name = getString(R.string.text_name_notification); int importance = NotificationManager.IMPORTANCE_DEFAULT; NotificationChannel channel = new NotificationChannel(FOREGROUND_CHANNEL_ID, name, importance); channel.enableVibration(false); mNotificationManager.createNotificationChannel(channel); } Intent notificationIntent = new Intent(this, MainActivity.class); notificationIntent.setAction(ACTION_MAIN); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); // if min sdk goes below honeycomb /*if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) { notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); } else { notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); }*/ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); // make a stop intent Intent stopIntent = new Intent(this, MicroPhotoService.class); stopIntent.setAction(ACTION_STOP); PendingIntent pendingStopIntent = PendingIntent.getService(this, 0, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT); RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.notification); remoteViews.setOnClickPendingIntent(R.id.btn_stop, pendingStopIntent); // if it is connected switch(stateService) { case STATE_SERVICE.NOT_CONNECTED: remoteViews.setTextViewText(R.id.tv_state, "DISCONNECTED"); break; case STATE_SERVICE.CONNECTED: remoteViews.setTextViewText(R.id.tv_state, "CONNECTED"); break; } // notification builder NotificationCompat.Builder notificationBuilder; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { notificationBuilder = new NotificationCompat.Builder(this, FOREGROUND_CHANNEL_ID); } else { notificationBuilder = new NotificationCompat.Builder(this); } notificationBuilder .setContent(remoteViews) .setSmallIcon(R.mipmap.ic_launcher) .setCategory(NotificationCompat.CATEGORY_SERVICE) .setOnlyAlertOnce(true) .setOngoing(true) .setAutoCancel(true) .setContentIntent(pendingIntent); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { notificationBuilder.setVisibility(Notification.VISIBILITY_PUBLIC); } return notificationBuilder.build(); } /* public boolean takePhoto(short channel, short preset, String path) { boolean res = DSCameraManager.getInstace().takePhoto(path, channel, 0, 0); if (!res) { int idx = 0; do { res = DSCameraManager.getInstace().isCameraServiceOK(); if (!res) { try { Thread.sleep(1000L); } catch (InterruptedException ex) { // ex.printStackTrace(); } } } while(idx < 6); if (DSCameraManager.getInstace().isCameraServiceOK()) { res = DSCameraManager.getInstace().takePhoto(path, channel, 0, 0); } } return res; } */ @Override public void onDestroy() { stateService = STATE_SERVICE.NOT_CONNECTED; uninit(); DSCameraManager.getInstace().unInit(); super.onDestroy(); } protected boolean updateTime(long timeInMillis) { boolean res = false; try { // Calendar c = Calendar.getInstance(); // c.set(2010, 1, 1, 12, 00, 00); AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); am.setTime(timeInMillis); res = true; } catch (Exception ex) { int aa = 0; } return true; } public String buildPhotoDir(int channel) { File path = new File(Environment.getExternalStorageDirectory(), "com.xyp.mp/photos/"); if (!path.exists() && !path.mkdirs()) { return null; } String p = path.getAbsolutePath(); if (!p.endsWith(File.separator)) { p += File.separator; } return p; } private String buildPhotoFileName(int channel, int preset, long ts) { String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); String photoFile = "img_" + Integer.toString(channel) + "_" + Integer.toHexString(preset).toUpperCase() + "_" + date + ".jpg"; return photoFile; } protected native boolean init(String appPath, String ip, int port, String cmdid); protected native long getHeartbeatDuration(); protected native long[] getPhotoTimeData(); protected native boolean notifyToTakePhoto(int channel, int preset, long scheduleTime, String path, String fileName, boolean sendToCma); protected native boolean sendHeartbeat(); protected native boolean fireTimeout(long uid); protected native boolean uninit(); protected long mHandler = 0; private AlarmReceiver alarmReceiver = null; }