package f.f.freezer; import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; import androidx.annotation.NonNull; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.security.KeyManagementException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import io.flutter.embedding.android.FlutterActivity; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugins.GeneratedPluginRegistrant; import static f.f.freezer.Deezer.bytesToHex; public class MainActivity extends FlutterActivity { private static final String CHANNEL = "f.f.freezer/native"; private static final String EVENT_CHANNEL = "f.f.freezer/downloads"; EventChannel.EventSink eventSink; boolean serviceBound = false; Messenger serviceMessenger; Messenger activityMessenger; SQLiteDatabase db; StreamServer streamServer; //Data if started from intent String intentPreload; @Override public void onCreate(Bundle savedInstanceState) { Intent intent = getIntent(); intentPreload = intent.getStringExtra("preload"); super.onCreate(savedInstanceState); } @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { GeneratedPluginRegistrant.registerWith(flutterEngine); //Flutter method channel new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL).setMethodCallHandler((((call, result) -> { //Add downloads to DB, then refresh service if (call.method.equals("addDownloads")) { //TX db.beginTransaction(); ArrayList downloads = call.arguments(); for (int i=0; i 0) { //If done or error, set state to NONE - they should be skipped because file exists cursor.moveToNext(); if (cursor.getInt(1) >= 3) { ContentValues values = new ContentValues(); values.put("state", 0); values.put("quality", cursor.getInt(2)); db.update("Downloads", values, "id == ?", new String[]{Integer.toString(cursor.getInt(0))}); Log.d("INFO", "Already exists in DB, updating to none state!"); } else { Log.d("INFO", "Already exits in DB!"); } cursor.close(); continue; } cursor.close(); //Insert ContentValues row = Download.flutterToSQL(downloads.get(i)); db.insert("Downloads", null, row); } db.setTransactionSuccessful(); db.endTransaction(); //Update service sendMessage(DownloadService.SERVICE_LOAD_DOWNLOADS, null); result.success(null); return; } //Get all downloads from DB if (call.method.equals("getDownloads")) { Cursor cursor = db.query("Downloads", null, null, null, null, null, null); ArrayList downloads = new ArrayList(); //Parse downloads while (cursor.moveToNext()) { Download download = Download.fromSQL(cursor); downloads.add(download.toHashMap()); } cursor.close(); result.success(downloads); return; } //Update settings from UI if (call.method.equals("updateSettings")) { Bundle bundle = new Bundle(); bundle.putString("json", call.argument("json").toString()); sendMessage(DownloadService.SERVICE_SETTINGS_UPDATE, bundle); result.success(null); return; } //Load downloads from DB in service if (call.method.equals("loadDownloads")) { sendMessage(DownloadService.SERVICE_LOAD_DOWNLOADS, null); result.success(null); return; } //Start/Resume downloading if (call.method.equals("start")) { sendMessage(DownloadService.SERVICE_START_DOWNLOAD, null); result.success(null); return; } //Stop downloading if (call.method.equals("stop")) { sendMessage(DownloadService.SERVICE_STOP_DOWNLOADS, null); result.success(null); return; } //Remove download if (call.method.equals("removeDownload")) { Bundle bundle = new Bundle(); bundle.putInt("id", (int)call.argument("id")); sendMessage(DownloadService.SERVICE_REMOVE_DOWNLOAD, bundle); result.success(null); return; } //Retry download if (call.method.equals("retryDownloads")) { sendMessage(DownloadService.SERVICE_RETRY_DOWNLOADS, null); result.success(null); return; } //Remove downloads by state if (call.method.equals("removeDownloads")) { Bundle bundle = new Bundle(); bundle.putInt("state", (int)call.argument("state")); sendMessage(DownloadService.SERVICE_REMOVE_DOWNLOADS, bundle); result.success(null); return; } //If app was started with preload info (Android Auto) if (call.method.equals("getPreloadInfo")) { result.success(intentPreload); intentPreload = null; return; } //Get architecture if (call.method.equals("arch")) { result.success(System.getProperty("os.arch")); return; } //Start streaming server if (call.method.equals("startServer")) { if (streamServer == null) { //Get offline path String offlinePath = getExternalFilesDir("offline").getAbsolutePath(); //Start server streamServer = new StreamServer(call.argument("arl"), offlinePath); streamServer.start(); } result.success(null); return; } //Get quality info from stream if (call.method.equals("getStreamInfo")) { if (streamServer == null) { result.success(null); return; } StreamServer.StreamInfo info = streamServer.streams.get(call.argument("id").toString()); if (info != null) result.success(info.toJSON()); else result.success(null); return; } result.error("0", "Not implemented!", "Not implemented!"); }))); //Event channel (for download updates) EventChannel eventChannel = new EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), EVENT_CHANNEL); eventChannel.setStreamHandler((new EventChannel.StreamHandler() { @Override public void onListen(Object arguments, EventChannel.EventSink events) { eventSink = events; } @Override public void onCancel(Object arguments) { eventSink = null; } })); } @Override protected void onStart() { super.onStart(); //Bind downloader service activityMessenger = new Messenger(new IncomingHandler(this)); Intent intent = new Intent(this, DownloadService.class); intent.putExtra("activityMessenger", activityMessenger); startService(intent); bindService(intent, connection, 0); //Get DB DownloadsDatabase dbHelper = new DownloadsDatabase(getApplicationContext()); db = dbHelper.getWritableDatabase(); //Trust all SSL Certs - Credits to Kilowatt36 TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { public java.security.cert.X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] certs, String authType) { } public void checkServerTrusted(X509Certificate[] certs, String authType) { } } }; SSLContext sc; try { sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); } catch (NoSuchAlgorithmException | KeyManagementException e) { Log.e(this.getLocalClassName(), e.getMessage()); } } @Override protected void onStop() { super.onStop(); //Unbind service on exit if (serviceBound) { unbindService(connection); serviceBound = false; } db.close(); } @Override protected void onDestroy() { super.onDestroy(); //Stop server if (streamServer != null) streamServer.stop(); } //Connection to download service private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { serviceMessenger = new Messenger(iBinder); serviceBound = true; } @Override public void onServiceDisconnected(ComponentName componentName) { serviceMessenger = null; serviceBound = false; } }; //Handler for incoming messages from service class IncomingHandler extends Handler { IncomingHandler(Context context) { Context applicationContext = context.getApplicationContext(); } @Override public void handleMessage(Message msg) { switch (msg.what) { //Forward to flutter. case DownloadService.SERVICE_ON_PROGRESS: if (eventSink == null) break; if (msg.getData().getParcelableArrayList("downloads").size() > 0) { //Generate HashMap ArrayList for sending to flutter ArrayList data = new ArrayList<>(); for (int i=0; i