package com.algobase.service; import java.io.IOException; import java.io.File; import java.io.FileReader; import java.io.BufferedReader; import java.io.FileWriter; import java.text.SimpleDateFormat; import java.util.Locale; import java.util.List; import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; import android.os.Environment; import android.os.Binder; import android.os.Handler; import android.os.Looper; import android.os.IBinder; import android.os.Build; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.BroadcastReceiver; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.Configuration; import android.net.Uri; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Typeface; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Icon; import android.app.PendingIntent; import android.app.ActivityManager; import android.app.ActivityManager.RunningServiceInfo; import android.provider.Settings; import android.app.Notification; import android.app.NotificationManager; import android.app.NotificationChannel; import android.view.View; import android.widget.Toast; import android.widget.RemoteViews; import android.util.Log; import com.samsung.android.sdk.SsdkUnsupportedException; import com.samsung.android.sdk.accessory.*; import com.algobase.stracks.sTracksActivity; import com.algobase.stracks.R; import com.algobase.share.compat.*; import com.algobase.share.system.*; import com.algobase.share.app.*; import com.algobase.share.dialog.*; import com.algobase.share.activity.*; public class ProviderService extends SAAgent { static final int NOTIFICATION_ID = 9661; static final int NOTIFICATION2_ID = 9662; static final String CHANNEL_ID = "sTracks_Provider_Channel_ID"; static final String CHANNEL_NAME = "sTracks Accessory Provider Channel1"; static final String CHANNEL2_ID = "sTracks_Provider_Channel2_ID"; static final String CHANNEL2_NAME = "sTracks Accessory Provider Channel2"; public static final String SCREEN_STATE = "sTracks_Provider_Screen_State"; public static final String CONNECTION_STATE = "sTracks_Provider_Connection_State"; public static final String TRACK_CMD = "sTracks_Provider_Track_Cmd"; static final String TAG = "ProviderService"; static final Class SASOCKET_CLASS = ServiceConnection.class; final IBinder binder = new LocalBinder(); ServiceConnection connectionHandler = null; BroadcastReceiver receiver; Notification.Builder notificationBuilder; NotificationManager notificationManager; FileWriter log_writer; Bitmap icon_screen_on; Bitmap icon_screen_off; boolean screen_on = true; int num_pages = 3; int last_ascent = 0; int delta_ascent = 100; // 100 m int last_dist = 0; int delta_dist = 10000; // 10 km String pkg_name; String language = "English"; String user_name = ""; String peer_name = ""; String product_id = ""; String error_title = null; String error_msg = null; int error_count = 0; String trk_state = "stopped"; String trk_name = "No Track"; float trk_dist = 0; int trk_alt = 0; int trk_asc = 0; int trk_hrt = 0; int trk_hrtmax = 0; int trk_pwr = 0; float trk_spd = 0; float trk_avg_spd = 0; long trk_tm = 0; long trk_brk = 0; double crs_current_dist = -1; double crs_current_ascent = 0; double crs_remaining_dist = 0; double crs_remaining_ascent = 0; double crs_dist_to_course = 0; boolean crs_on_track = true; public class ServiceConnection extends SASocket { public ServiceConnection() { super(ServiceConnection.class.getName()); log("ServiceConnection " + ServiceConnection.class.getName()); } @Override public void onError(int channelId, String errorMessage, int errorCode) { log("ServiceConnection.onError: " + errorMessage); } @Override public void onReceive(int channelId, byte[] data) { String cmd = new String(data); // possible commands // "START" // "EXIT" // "SCREEN ON" // "SCREEN OFF" // "GET PROVIDER" // "GET i" (i = page index) //log("onReceive: channel = " + channelId + " cmd = " + cmd); log("onReceive: cmd = " + cmd); if (cmd.startsWith("GET")) { String[] A = cmd.split(" "); if (A.length < 2) return; if (A[1].equals("PROVIDER")) { sendMessage(format("Provider:%s:%s:%d", user_name, Build.MODEL, num_pages)); return; } int i = Integer.parseInt(A[1]); if (screen_on) sendData(i); return; } if (cmd.equals("SCREEN OFF")) { if (screen_on) { //showToast("Screen off."); screen_on = false; broadcast(SCREEN_STATE,"0"); updateNotification(); } return; } if (cmd.equals("SCREEN ON")) { if (!screen_on) { //showToast("Screen on."); screen_on = true; broadcast(SCREEN_STATE,"1"); updateNotification(); } return; } if (cmd.equals("START")) { if (Build.VERSION.SDK_INT >= 29 && !dataServiceRunning() && checkSelfPermission(sTracksActivity.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) { error_title = "Missing Permmission"; error_msg = "See the
" + "Notification
" + "on your phone.
" + "


"; if (language.equals("Deutsch")) { error_title = "Fehlende Berechtigung"; error_msg = "Siehe aktuelle
" + "Benachrichtigung
" + "auf deinem Phone.
" + "


"; } error_count = 5; //sendData(-1); if (language.equals("Deutsch")) notifyPopup("START von der Uhr (Galaxy Watch)", "Standortzugriff 'Immer zulassen'", "Berechtigung erteilen"); else notifyPopup("START from Watch (Galaxy Watch)", "Location Access 'Always allow'", "Grant Permission"); return; } GregorianCalendar cal = new GregorianCalendar(); Date date = cal.getTime(); final String trk_name = new SimpleDateFormat("yyyy-MM-dd-HH-mm").format(date); showToast("START " + trk_name); new MyThread() { public void run() { startDataService("connect",null); sleep(500); startDataService("begin",trk_name); sleep(500); startDataService("start",null); sleep(500); broadcast(TRACK_CMD,"start"); } }.start(); return; } if (cmd.equals("EXIT")) { new MyThread() { public void run() { sleep(500); stopSelf(); } }.start(); return; } showToast("unknown cmd: " + cmd); } @Override protected void onServiceConnectionLost(int reason) { log("onServiceConnection lost."); //showToast("Provider: Connection lost."); connectionHandler = null; stopSelf(); } } public class LocalBinder extends Binder { public ProviderService getService() { log("LocalBinder.getService"); return ProviderService.this; } } public ProviderService() { super(TAG, SASOCKET_CLASS); } String format(String pattern, Object... args) { return String.format(Locale.US,pattern,args); } void writeLog(FileWriter writer, String msg) { if (writer == null) return; try { writer.write(msg,0,msg.length()); writer.write('\n'); writer.flush(); } catch(IOException e){ } } void log(String txt) { Log.d(TAG, txt); writeLog(log_writer,txt); } void showToast(final String msg) { Handler handler = new Handler(Looper.getMainLooper()); handler.post( new Runnable() { public void run() { Toast.makeText(getBaseContext(), msg,Toast.LENGTH_SHORT).show(); } }); } String colon_correction(String s) { String[] A = s.split(":"); String txt = A[0]; for(int i=1; i"; String td_left = ""; String td_right1 = ""; String td_right2 = ""; String trk_clr = "#cccccc"; if (trk_state.equals("moving")) trk_clr = "#88cc88"; else if (trk_state.equals("break")) trk_clr = "#ee4050"; else trk_clr = "#cccccc"; // vh:percent of viewport height // vw:percent of viewport width //String fsz = "12.50vh"; // 0.125 * 360 = 45.0 px String fsz = "13.00vh"; // 0.130 * 360 = 46.8 px String html = "
"; html += "
"; html += colon_correction(trk_name); html += "
"; html += ""; html += ""; html += td_left + time_to_hms(trk_tm) + ""; if (trk_dist < 1000) { html += td_right1 + format("%.0f",trk_dist) + ""; html += td_right2 + "m" + ""; } else if (trk_dist < 10000) { html += td_right1 + format("%.2f",trk_dist/1000) + ""; html += td_right2 + "km" + ""; } else { html += td_right1 + format("%.1f",trk_dist/1000) + ""; html += td_right2 + "km" + ""; } html += ""; html += ""; if (trk_hrt > 0) { html += td_left; html += "♡"; html += ""; html += trk_hrt; html += ""; } else html += td_left + time_to_ms(trk_brk) + ""; html += td_right1 + trk_asc + ""; html += td_right2 + "↑ " + ""; html += ""; html += ""; if (trk_avg_spd < 10) html += td_span + format("  %.1f km/h",trk_avg_spd) + ""; else html += td_span + format("%.1f km/h",trk_avg_spd) + ""; html += ""; html += "
"; html += "
"; sendMessage(html); } void msg_page(String title, String msg) { String html = ""; html += "
"; html += "
"; html += title; html += "
"; html += "
"; html += msg; html += "
"; html += "
"; sendMessage(html); } void page(String title, String[] lines, String clr) { String html = ""; //html += "
"; html += ""; html += ""; } html += ""; } html += "
1) { html += ""; html += A[1]; html += "
"; sendMessage(html); } void start_page() { String html = ""; html += "
"; html += "sTracks"; if (pkg_name.endsWith("devel")) html += "-D"; html += "
"; html += "
"; html += Build.MODEL; html += "
"; html += "
"; html += "
"; html += ""; html += "
"; html += "
"; sendMessage(html); } public void sendData(int index) { if (error_msg != null && error_count > 0) { msg_page(error_title,error_msg); error_count--; return; } switch (index) { case -1: msg_page(error_title,error_msg); break; case 0: if (trk_state.equals("stopped")) start_page(); else main_page(); break; case 1: { if (trk_hrt <= 0) msg_page("Heartrate", "No HR-Sensor connected to phone.
"); else { String[] lines = new String[2]; lines[0] = format("%d:bpm",trk_hrt); lines[1] = format("%d:max",trk_hrtmax); page("Heartrate",lines,"#ffffff"); } break; } case 2: { if (crs_current_dist == -1) msg_page("Course", "No active course.

"); else { String[] lines = new String[1]; lines[0] = format("%.0f:m", crs_dist_to_course); String clr = crs_on_track ? "#ffffff" : "#aa0000"; page("Distance to Course",lines,clr); } break; } /* case 3: { if (trk_pwr == -1) msg_page("Power", "No PWR-Sensor connected to phone.
"); else { String[] lines = new String[1]; lines[0] = format("%d:watt",trk_pwr); page("Power",lines,"#ffffff"); } break; } case 4: { String[] lines = new String[2]; lines[0] = format("%.1f:km/h", trk_spd); lines[1] = format("%.1f:  Ø",trk_avg_spd); page("Speed",lines,"#ffffff"); break; } */ } } void startDataService(String cmd, String param) { Context context = getBaseContext(); Intent intent = new Intent(context, DataService.class); if (cmd != null) intent.putExtra("cmd",cmd); if (param != null) intent.putExtra("param1",param); if (Build.VERSION.SDK_INT < 26) { startService(intent); return; } try { startForegroundService(intent); } catch (Exception ex) { showToast("Exception " + ex.toString()); } } /* void startNotification() { log(""); log("start notification"); notificationManager = getSystemService(NotificationManager.class); if (Build.VERSION.SDK_INT < 26) notificationBuilder = new Notification.Builder(this); else { NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManagerIMPORTANCE_LOW); notificationManager.createNotificationChannel(channel); notificationBuilder = new Notification.Builder(this,CHANNEL_ID); } notificationBuilder.setSmallIcon(R.drawable.ic_schedule1); notificationBuilder.setOngoing(true); notificationBuilder.setVisibility(Notification.VISIBILITY_SECRET); startForeground(NOTIFICATION_ID,notificationBuilder.build()); } */ void updateNotification() { //String title = "Provider Service"; //String title = sTracksActivity.APP_NAME; String text = peer_name + " " + product_id; if (product_id.equals("SM-R600")) text = "Gear Sport"; if (product_id.equals("SM-R760")) text = "Gear S3 frontier"; if (product_id.equals("SM-R770")) text = "Gear S3 classic"; if (product_id.equals("SM-R800")) text = "Galaxy Watch 46 mm "; if (product_id.equals("SM-R805")) text = "Galaxy Watch LTE 46 mm "; if (product_id.equals("SM-R810")) text = "Galaxy Watch 42 mm"; if (product_id.equals("SM-R815")) text = "Galaxy Watch LTE 42 mm"; String title = text; text = pkg_name; int title_clr = 0xffeeeeee; int text_clr = 0xffeeeeee; int uimode = getResources().getConfiguration().uiMode; int flags = uimode & Configuration.UI_MODE_NIGHT_MASK; if (flags == Configuration.UI_MODE_NIGHT_NO) { title_clr = 0xff111111; text_clr = 0xff111111; } else { title_clr = 0xffcccccc; text_clr = 0xffcccccc; title = " " + title; text = " " + text; } RemoteViews rviews= new RemoteViews(pkg_name,R.layout.notification); /* if (screen_on) rviews.setImageViewBitmap(R.id.notification_image,icon_screen_on); else rviews.setImageViewBitmap(R.id.notification_image,icon_screen_off); */ rviews.setTextColor(R.id.notification_title,title_clr); rviews.setTextColor(R.id.notification_text,text_clr); rviews.setTextViewText(R.id.notification_title, title); rviews.setTextViewText(R.id.notification_text, text); if (Build.VERSION.SDK_INT < 24) { notificationBuilder.setContentText(text); } else { //notificationBuilder.setContent(rviews); notificationBuilder.setCustomContentView(rviews); notificationBuilder.setStyle(new Notification.DecoratedCustomViewStyle()); } if (screen_on) notificationBuilder.setColor(0xff00a000); else notificationBuilder.setColor(0xffa00000); notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build()); } void notifyPopup(String title, String text, String button) { if (Build.VERSION.SDK_INT < 26) return; /* Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package",pkg_name,null); intent.setData(uri); */ Intent intent = new Intent(this, sTracksActivity.class); intent.putExtra("permission",sTracksActivity.ACCESS_BACKGROUND_LOCATION); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pending_intent = PendingIntent.getActivity(this,0,intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); Notification.Builder builder = new Notification.Builder(this, CHANNEL2_ID) .setSmallIcon(R.drawable.ic_schedule1) .setContentTitle(title) .setContentText(text) .setColor(0xffa00000) .addAction(0,button,pending_intent) .setStyle(new Notification.BigTextStyle().bigText(text)); notificationManager.notify(NOTIFICATION2_ID, builder.build()); } void broadcast(String action, String value) { Intent intent = new Intent(action); if (value != null) intent.putExtra("value",value); sendBroadcast(intent); } public boolean dataServiceRunning() { Context context = getBaseContext(); ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); List L = manager.getRunningServices(Integer.MAX_VALUE); for (RunningServiceInfo rsi : L) { String class_name = rsi.service.getClassName(); if (class_name.equals(DataService.class.getName())) { return true; } } return false; } @Override public void onCreate() { super.onCreate(); File stracks_folder = getFilesDir(); File log_folder = new File(stracks_folder,"log"); File log_file = new File(log_folder,"provider_log.txt"); try { log_writer = new FileWriter(log_file); } catch (IOException e) { showToast(e.toString()); } log("onCreate"); pkg_name = getPackageName(); screen_on = true; icon_screen_on = MyBitmap.bitmap_from_drawable(this, R.drawable.ic_schedule1, 0xff00a000); icon_screen_off = MyBitmap.bitmap_from_drawable(this, R.drawable.ic_schedule1, 0xffa00000); String prefs_file = sTracksActivity.PREFS_NAME; SharedPreferences prefs = getSharedPreferences(prefs_file,0); user_name = prefs.getString("user",""); language = prefs.getString("language","English"); log("user_name = " + user_name); //showToast("user_name = " + user_name); SA mAccessory = new SA(); try { mAccessory.initialize(this); } catch (SsdkUnsupportedException e) { log("SsdkUnsupportedException: " + e.toString()); //processUnsupportedException(e); } catch (Exception e) { log("Exception " + e.toString()); e.printStackTrace(); stopSelf(); } receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (!action.equals(DataService.UPDATE_PROVIDER)) return; /* String state = intent.getStringExtra("state"); if (!trk_state.equals(state)) showToast(state); */ trk_state = intent.getStringExtra("state"); trk_name = intent.getStringExtra("name"); trk_tm = intent.getLongExtra("time",0); trk_brk = intent.getLongExtra("break",0); trk_dist = intent.getFloatExtra("dist",0); trk_alt = (int)intent.getDoubleExtra("alt",0); trk_asc = (int)intent.getDoubleExtra("asc",0); trk_hrt = intent.getIntExtra("hrt",0); trk_hrtmax = intent.getIntExtra("hrtmax",0); trk_pwr = intent.getIntExtra("pwr",0); trk_spd = 3.6f*intent.getFloatExtra("spd",0); trk_avg_spd = (trk_tm > 0) ? (3600.0f*trk_dist)/trk_tm : 0; crs_current_dist = intent.getDoubleExtra("crs_dst",-1); crs_current_ascent = intent.getDoubleExtra("crs_asc",0); crs_dist_to_course = intent.getDoubleExtra("crs_closest_dst",0); crs_on_track = intent.getBooleanExtra("crs_on_track",true); /* crs_remaining_dist = course_total_dist - course_current_dist; crs_remaining_ascent = course_total_ascent - course_current_ascent; */ if (trk_asc > last_ascent + delta_ascent) { while(trk_asc > last_ascent + delta_ascent) last_ascent += delta_ascent; sendMessage("vibrate 150 100 150"); } if (trk_dist > last_dist + delta_dist) { while(trk_dist > last_dist + delta_dist) last_dist += delta_dist; sendMessage("vibrate 150 100 150 100 150"); } } }; IntentFilter filter = new IntentFilter(); //filter.addAction(DataService.UPDATE_TRACKPOINT); filter.addAction(DataService.UPDATE_PROVIDER); /* Context context = getBaseContext(); context.registerReceiver(receiver,filter); */ registerReceiver(receiver,filter); log(""); log("start notification"); notificationManager = getSystemService(NotificationManager.class); if (Build.VERSION.SDK_INT >= 26) { NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW); notificationManager.createNotificationChannel(channel); // popup: channel2 (high importance) NotificationChannel channel2 = new NotificationChannel(CHANNEL2_ID,CHANNEL2_NAME, NotificationManager.IMPORTANCE_MAX); notificationManager.createNotificationChannel(channel2); } notificationBuilder = new Notification.Builder(this,CHANNEL_ID); notificationBuilder.setSmallIcon(R.drawable.ic_schedule1); notificationBuilder.setOngoing(true); if (Build.VERSION.SDK_INT >= 21) { notificationBuilder.setVisibility(Notification.VISIBILITY_SECRET); } try { startForeground(NOTIFICATION_ID,notificationBuilder.build()); } catch (Exception ex) { showToast("Exception " + ex.toString()); } broadcast(CONNECTION_STATE,"1"); //showToast("Provider started."); } @Override protected void onFindPeerAgentsResponse(SAPeerAgent[] peerAgents, int result) { log("onFindPeerAgentResponse : result =" + result); } @Override protected void onServiceConnectionRequested(SAPeerAgent peerAgent) { log("onServiceConnectionRequested"); if (peerAgent == null) log("peerAgent = null"); else acceptServiceConnectionRequest(peerAgent); } @Override protected void onServiceConnectionResponse(SAPeerAgent peerAgent, SASocket socket, int result) { String s = ""; switch(result) { case CONNECTION_ALREADY_EXIST: s = "CONNECTION_ALREADY_EXIST"; break; case CONNECTION_DUPLICATE_REQUEST: s = "CONNECTION_DUPLICATE_REQUEST"; break; case CONNECTION_FAILURE_DEVICE_UNREACHABLE: s = "CONNECTION_FAILURE_DEVICE_UNREACHABLE"; break; case CONNECTION_FAILURE_INVALID_PEERAGENT: s = "CONNECTION_FAILURE_INVALID_PEERAGENT"; break; case CONNECTION_FAILURE_NETWORK: s = "CONNECTION_FAILURE_NETWORK"; break; case CONNECTION_FAILURE_PEERAGENT_NO_RESPONSE: s = "CONNECTION_FAILURE_PEERAGENT_NO_RESPONSE"; break; case CONNECTION_FAILURE_PEERAGENT_REJECTED: s = "CONNECTION_FAILURE_PEERAGENT_REJECTED"; break; case CONNECTION_FAILURE_SERVICE_LIMIT_REACHED: s = "CONNECTION_FAILURE_SERVICE_LIMIT_REACHED"; break; case CONNECTION_SUCCESS: s = "CONNECTION_SUCCESS"; break; } log("onServiceConnectionResponse: " + s); //if (result != SAAgentV2.CONNECTION_SUCCESS) return; if (result != SAAgent.CONNECTION_SUCCESS) return; if (socket == null) return; screen_on = true; broadcast(SCREEN_STATE,"1"); connectionHandler = (ServiceConnection) socket; SAPeerAccessory acc = peerAgent.getAccessory(); product_id = acc.getProductId(); peer_name = acc.getName(); updateNotification(); } @Override protected void onAuthenticationResponse(SAPeerAgent peerAgent, SAAuthenticationToken authToken, int error) { log("onAuthenticationResponse"); /* The authenticatePeerAgent(peerAgent) API may not be working properly depending on the firmware version of accessory device. Please refer to another sample application for Security. */ } @Override protected void onError(SAPeerAgent peerAgent, String errorMessage, int errorCode) { log("onError"); super.onError(peerAgent, errorMessage, errorCode); } /* void processUnsupportedException(SsdkUnsupportedException e) { int errType = e.getType(); if (errType == SsdkUnsupportedException.VENDOR_NOT_SUPPORTED) { log("Vendor not supported."); } if (errType == SsdkUnsupportedException.DEVICE_NOT_SUPPORTED) { log("Device not supported."); } if (errType == SsdkUnsupportedException.LIBRARY_NOT_INSTALLED) { log("Samsung Accessory SDK no installed."); } if (errType == SsdkUnsupportedException.LIBRARY_UPDATE_IS_REQUIRED) { log("Samsung Accessory SDK update required."); } if (errType == SsdkUnsupportedException.LIBRARY_UPDATE_IS_RECOMMENDED) { log("Samsung Accessory SDK update recommended."); } } */ @Override public void onDestroy() { log("onDestroy"); broadcast(CONNECTION_STATE,"0"); //showToast("Provider stopped."); //getBaseContext().unregisterReceiver(receiver); unregisterReceiver(receiver); super.onDestroy(); } @Override public void onTaskRemoved(Intent intent) { log("onTaskRemoved"); super.onTaskRemoved(intent); } @Override public void onLowMemory() { log("onLowMemory"); super.onLowMemory(); } @Override public IBinder onBind(Intent intent) { log("onBind"); return binder; } }