package com.xixun.xixunplayer; import android.annotation.SuppressLint; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.os.StrictMode; import android.view.Choreographer; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.xixun.joey.aidlset.CardService; import com.xixun.xy.conn.aidl.ConnService; import net.lingala.zip4j.ZipFile; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.net.ServerSocket; import java.util.ArrayList; import java.util.HashSet; import gnph.util.Chsets; import gnph.util.IOs; import gnph.util.JSList; import gnph.util.JSMap; public class MainActivity extends Activity implements Choreographer.FrameCallback, Runnable { public static MainActivity ins; ArrayList reces = new ArrayList<>(); public Intent environIntent = new Intent(); HashSet environs = new HashSet<>(); BackView backView; Prog progView, insView; long launchMilli = System.currentTimeMillis(); long syncMs; int state; @RequiresApi(api = Build.VERSION_CODES.R) @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ins = this; Util.println("==>> MainActivity onCreate >>>> this "+hashCode()+" UI Thread: "+Thread.currentThread().getId()); try{ if(RestartService.ins==null) startService(new Intent(this, RestartService.class)); } catch(Throwable e) { Util.printStackTrace(e); } StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().permitAll().build()); if(ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) init(); else { Util.println("---- No permission, Try again ..."); ActivityCompat.requestPermissions(this, new String[]{ android.Manifest.permission.WRITE_EXTERNAL_STORAGE, android.Manifest.permission.READ_EXTERNAL_STORAGE, android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, android.Manifest.permission.RECEIVE_BOOT_COMPLETED, android.Manifest.permission.INTERNET }, 999); } } @Override protected void onDestroy() { super.onDestroy(); Util.println("==<< MainActivity onDestroy <<<< this "+hashCode()); state = 8; if(insView!=null) { insView.release(); insView = null; } if(progView!=null) { progView.release(); progView = null; } ins = null; for(var rece : reces) { try { unregisterReceiver(rece); } catch (Throwable ignored){ } } System.gc(); } @Override protected void onStart() { super.onStart(); Util.println(" ==>> MainActivity onStart >> "+hashCode()); } @Override protected void onStop() { super.onStop(); Util.println(" ==<< MainActivity onStop << "+hashCode()); } @Override protected void onRestart() { super.onRestart(); Util.println(" ==>> MainActivity onRestart >> "+hashCode()); } @Override protected void onResume() { super.onResume(); Util.println(" ==>> MainActivity onResume >> "+hashCode()); } @Override protected void onPause() { super.onPause(); Util.println(" ==<< MainActivity onPause << "+hashCode()); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if(requestCode==999 && grantResults.length > 0 && grantResults[0]==PackageManager.PERMISSION_GRANTED && backView==null) init(); } ConnService serviXy; @SuppressLint("UnspecifiedRegisterReceiverFlag") public void init() { var dir = Build.VERSION.SDK_INT < Build.VERSION_CODES.R ? Environment.getExternalStorageDirectory().getAbsolutePath() + "/XixunPlayer" : getExternalFilesDir(null).getAbsolutePath(); var msg = "---- dir "+dir; Util.println(msg); Util.programDir = dir + "/program"; Util.backImgFile = dir + "/background"; var program = new File(Util.programDir); if(program.isFile()) program.delete(); msg = "---- mkdir: "+program.mkdirs(); Util.println(msg); var cardConn = new ServiceConnection() { public void onServiceDisconnected(ComponentName name) { Util.println("<-<- AIDL Service cardsystem Disconnected"); } public void onServiceConnected(ComponentName name, IBinder iBinder) { unbindService(this); Util.println("->-> AIDL Service cardsystem aidl service Connected"); var service = CardService.Stub.asInterface(iBinder); try { Util.isScreenOn = service.isScreenOpen(); Util.screenWidth = service.getScreenWidth(); Util.screenHeight = service.getScreenHeight(); Util.println(" IsScreenOn: "+Util.isScreenOn+" screen: "+Util.screenWidth+" x "+Util.screenHeight); setContentView(backView = new BackView(MainActivity.this, Util.screenWidth, Util.screenHeight)); state = 5; if(Util.isScreenOn) initProg(); else state = 8; } catch (Exception e) { Util.makeText(MainActivity.this, Util.toStr(e)).show(); Util.printStackTrace(e); } } }; var intent = new Intent("com.xixun.joey.aidlset.SettingsService"); intent.setPackage("com.xixun.joey.cardsystem"); bindService(intent, cardConn, Context.BIND_AUTO_CREATE); var connXy = new ServiceConnection() { public void onServiceDisconnected(ComponentName name) { Util.println("<-<- AIDL Service xy.conn Disconnected"); serviXy = null; } public void onServiceConnected(ComponentName name, IBinder iBinder) { unbindService(this); Util.println("->-> AIDL Service xy.conn Connected"); serviXy = ConnService.Stub.asInterface(iBinder); try { Util.serverURL = serviXy.getServerURL(); Util.println(" ServerURL: "+Util.serverURL); if(Util.serverURL==null || Util.serverURL.isEmpty()) Util.serverURL = "https://m2mled.net/"; else { if(! Util.serverURL.startsWith("http")) Util.serverURL = "http://"+Util.serverURL; if(! Util.serverURL.endsWith("/")) Util.serverURL += "/"; } } catch (Exception e) { Util.makeText(MainActivity.this, Util.toStr(e)).show(); Util.printStackTrace(e); } } }; var intenXy = new Intent("xixun.intent.action.CONNECTION_INFO"); intenXy.setPackage("com.xixun.xy.conn"); bindService(intenXy, connXy, Context.BIND_AUTO_CREATE); reces.clear(); BroadcastReceiver rece; registerReceiver(rece = new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { Util.println("Receive PAUSE_PLAYER"); Util.isScreenOn = ! intent.getBooleanExtra("pause", false); Util.println(" IsScreenOn: "+Util.isScreenOn); if(! Util.isScreenOn) { state = 8; if(insView!=null) { insView.release(); insView = null; } if(progView!=null) { progView.release(); progView = null; } setContentView(backView); } else if(progView==null && insView==null) MainActivity.ins.runOnUiThread(() -> initProg()); System.gc(); } }, new IntentFilter("com.xixun.action.PAUSE_PLAYER")); reces.add(rece); registerReceiver(rece = new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { Util.println("Receive CHANGE_COMPANYID"); try { if(serviXy!=null && serviXy.asBinder().isBinderAlive()) { Util.serverURL = serviXy.getServerURL(); Util.println(" ServerURL: "+Util.serverURL); if(Util.serverURL==null || Util.serverURL.isEmpty()) Util.serverURL = "https://m2mled.net/"; else { if(! Util.serverURL.startsWith("http")) Util.serverURL = "http://"+Util.serverURL; if(! Util.serverURL.endsWith("/")) Util.serverURL += "/"; } } else { Util.println(" bindService"); bindService(intenXy, connXy, Context.BIND_AUTO_CREATE); } } catch (Exception e) { Util.makeText(MainActivity.this, Util.toStr(e)).show(); Util.printStackTrace(e); } } }, new IntentFilter("com.xixun.joey.CHANGE_COMPANYID")); reces.add(rece); registerReceiver(rece = new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { MainActivity.this.environIntent = intent; for(var environ : environs) { environ.onReceive(intent); ((View)environ).invalidate(); } } }, new IntentFilter("xixun.intent.action.TEMPERATURE_HUMIDITY")); reces.add(rece); registerReceiver(rece = new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { try { var code = intent.getIntExtra("code", 0); Util.println(" remote_control "+code); if(progView == null || ! progView.isShown() || code > progView.pages.size()) return; var page = progView.pages.get(code-1); var ms = System.currentTimeMillis(); if(! avas.isEmpty()) { avas.get(curAva).hide(); if(avas.size()==1 && avas.get(0)==page) for(var call : progView.calls) call.doFrame(ms); } ms = (ms+999)/1000*1000; if(code > 0) { avas.clear(); avas.add(page); curAva = 0; curTimes = 1; waitTo = 0; //点播 page.setMillis(ms); if(state != 6) { setContentView(progView); state = 6; } } else { waitTo = Long.MAX_VALUE; syncProg(ms); } } catch (Throwable e) { Util.makeText(MainActivity.this, Util.toStr(e)).show(); Util.printStackTrace(e); } } }, new IntentFilter("com.xixun.yzd.REMOTE_CONTROL")); reces.add(rece); var intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); intentFilter.addDataScheme("file"); registerReceiver(rece = new BroadcastReceiver(){ long lastMs; final char[] pass = {'8','8','8'}; @Override public void onReceive(Context context, Intent intent) { var path = intent.getData().getPath(); Util.println("\nMEDIA_MOUNTED path: "+path); var ms = System.currentTimeMillis(); if(ms-lastMs<1000) return; lastMs = ms; Util.makeText(MainActivity.this, "MEDIA_MOUNTED path: "+path+"\nImporting 正在导入 ...").show(); new Thread(()->{ try { var zip = new ZipFile(path+"/program.zip"); if(zip.isEncrypted()) zip.setPassword(pass); long size = 0; ByteArrayOutputStream jsonOut = null; var headers = zip.getFileHeaders(); for(var header : headers) { size += header.getUncompressedSize(); if("program".equals(header.getFileName())) IOs.writeClose(jsonOut = new ByteArrayOutputStream(), zip.getInputStream(header)); } if(jsonOut==null) { Util.println("No program File"); runOnUiThread(() -> Util.makeText(MainActivity.this, "No program File").show()); return; } if(size==0) { Util.println("zip size is 0"); runOnUiThread(() -> Util.makeText(MainActivity.this, "zip size is 0").show()); return; } Util.deleteFiles(size, null); for(var header : headers) if(! "program".equals(header.getFileName())) { Util.println(" name: " + header.getFileName()); var fOut = new FileOutputStream(Util.programDir + "/" + header.getFileName()); IOs.writeCloseIn(fOut, zip.getInputStream(header)); fOut.flush(); fOut.getFD().sync(); fOut.close(); } var json = jsonOut.toByteArray(); runOnUiThread(() -> { Util.println("Import Succeed"); Util.makeText(MainActivity.this, "Import Succeed 导入成功\nDon't shut down within 1 minute, otherwise the program may be lost\n不要在 1 分钟内关机,否则节目可能丢失").show(); initProg(json); }); } catch (Exception e) { Util.printStackTrace(e); runOnUiThread(() -> Util.makeText(MainActivity.this, Util.toStr(e)).show()); } }).start(); } }, intentFilter); reces.add(rece); new Thread(this).start(); } public void stopProg() { if(insView!=null) { insView.release(); insView = null; } if(progView!=null) { progView.release(); progView = null; } setContentView(backView); System.gc(); } public boolean delProgFile() { stopProg(); var files = new File(Util.programDir).listFiles(); var ok = true; if(files != null) for(var file : files) if(! file.delete()) ok = false; state = 4; try { var out = new FileOutputStream(Util.programDir+"/program"); out.write("{}".getBytes()); out.flush(); out.getFD().sync(); out.close(); } catch (Throwable ignored) { } return ok; } public void initProg() { state = 1; try { Util.println("\nParse Insert Prog Json"); var root = JSMap.fromClose(new BufferedInputStream(new FileInputStream(Util.programDir + "/insert"))); var task = root.jsmap("task"); if(task==null && root.containsKey("layers")) task = new JSMap("items", new JSList<>(new JSMap("_program", root))); if(task!=null) { var view = new Prog(task, this); if(view.getChildCount()!=0) setContentView(insView = view); } } catch(FileNotFoundException ignored) { } catch(Throwable e) { state = 7; Util.printStackTrace(e); } try { Util.println("\nParse Prog Json"); var root = JSMap.fromClose(new BufferedInputStream(new FileInputStream(Util.programDir + "/program"))); var task = root.jsmap("task"); if(task==null && root.containsKey("layers")) task = new JSMap("items", new JSList<>(new JSMap("_program", root))); if(task==null) Util.println(root.isEmpty() ? " Empty program JSON\n" : " Error: task==null\n"); else { var view = new Prog(task, this); if(view.getChildCount()==0) Util.println(" Error: ChildCount==0\n"); else setContentView(progView = view); } } catch(FileNotFoundException e) { Util.println(""+e); } catch(Throwable e) { state = 7; Util.makeText(this, Util.toStr(e)).show(); Util.printStackTrace(e); } if(insView!=null || progView!=null) { state = 5; Util.println("Init Sync"); syncProg((System.currentTimeMillis()+999)/1000*1000); if(canAdd) { choreographer.postFrameCallback(this); canAdd = false; } } else if(state != 7) state = 3; } public void initProg(byte[] json) { try { Util.println("\nParse Prog Json"); var root = JSMap.from(json); var task = root.jsmap("task"); if(task==null) { if(! root.containsKey("layers")) { state = 7; Util.println(" Error: task==null\n"); Util.println(new String(json, Chsets.UTF8)); return; } task = new JSMap("items", new JSList<>(new JSMap("_program", root))); } var view = new Prog(task, this); if(view.getChildCount()==0) { if(! view.isInsert) state = 7; Util.println(" Error: ChildCount==0\n"); Util.println(new String(json, Chsets.UTF8)); return; } if(view.isInsert) { if(insView!=null) insView.release(); insView = view; setContentView(insView); } else { if(progView!=null) progView.release(); progView = view; setContentView(progView); } var fOut = new FileOutputStream(Util.programDir + (view.isInsert?"/insert":"/program")); fOut.write(json); fOut.flush(); fOut.getFD().sync(); fOut.close(); state = 5; System.gc(); Util.println("Init Sync"); syncProg((System.currentTimeMillis()+999)/1000*1000); if(canAdd) { choreographer.postFrameCallback(this); canAdd = false; } } catch (Throwable e) { state = 7; Util.makeText(this, Util.toStr(e)).show(); Util.printStackTrace(e); Util.println(new String(json, Chsets.UTF8)); } } ArrayList avas = new ArrayList<>(); int curAva, curTimes = 1; long waitTo = Long.MAX_VALUE; boolean isInsert; Choreographer choreographer = Choreographer.getInstance(); boolean canAdd = true; @Override public void doFrame(long frameTimeNanos) { if(progView == null && insView==null) { canAdd = true; return; } choreographer.postFrameCallback(this); canAdd = false; var milli = System.currentTimeMillis(); if(avas.isEmpty()) { if(milli >= waitTo) { waitTo = Long.MAX_VALUE; Util.println("wait sync"); syncProg(milli); if(insView!=null) for(var call : insView.calls) call.doFrame(milli); if(progView!=null) for(var call : progView.calls) call.doFrame(milli); } return; } var lastPage = avas.get(curAva); if(milli < lastPage.endMilli) lastPage.showHideSrcs(milli); else { lastPage.hide(); if(isInsert) { if(--lastPage.repeatTimes<=0 && ++curAva >= avas.size()) { var isDiff = milli-lastPage.endMilli>=1000; syncProg(isDiff ? milli : lastPage.endMilli); if(insView!=null) for(var call : insView.calls) call.doFrame(milli); if(progView!=null) for(var call : progView.calls) call.doFrame(milli); return; } } else { if(waitTo > 0) { //waitTo==0 为点播,不换页 if(curTimes < lastPage.repeatTimes) curTimes++; else { curTimes = 1; if(++curAva >= avas.size()) { var isDiff = milli-lastPage.endMilli>=1000; syncProg(isDiff ? milli : lastPage.endMilli); if(insView!=null) for(var call : insView.calls) call.doFrame(milli); if(progView!=null) for(var call : progView.calls) call.doFrame(milli); return; } } } } avas.get(curAva).setMillis(lastPage.endMilli); Util.println("curAva: "+curAva+" endMs: "+avas.get(curAva).endMilli); } if(isInsert) for(var call : insView.calls) call.doFrame(milli); else for(var call : progView.calls) call.doFrame(milli); } void syncProg(long milli) { Util.println("\nSyncProg"); avas.clear(); curAva = 0; curTimes = 1; isInsert = false; if(insView!=null) { for(int i=0; i Util.makeText(MainActivity.ins, Util.toStr(e)).show()); Util.printStackTrace(e); } } } catch (Throwable e) { var msg = e.getMessage(); if(msg==null || ! msg.contains("EADDRINUSE")) MainActivity.ins.runOnUiThread(() -> Util.makeText(MainActivity.ins, Util.toStr(e)).show()); Util.printStackTrace(e); } } }