gaoyf 1 month ago
commit
45161989c2
86 changed files with 3729 additions and 0 deletions
  1. 17 0
      .gitignore
  2. 1 0
      app/.gitignore
  3. 48 0
      app/build.gradle
  4. BIN
      app/libs/NodeMediaClient-Android-2.7.3.aar
  5. BIN
      app/libs/apkloaderbase-release.aar
  6. BIN
      app/libs/gson-2.8.2.jar
  7. BIN
      app/libs/librtmp.aar
  8. 21 0
      app/proguard-rules.pro
  9. 26 0
      app/src/androidTest/java/com/example/plugina/ExampleInstrumentedTest.java
  10. 31 0
      app/src/main/AndroidManifest.xml
  11. 221 0
      app/src/main/java/com/example/MainActivity.java
  12. 39 0
      app/src/main/java/com/example/json/JsonBooleanAsIntTypeAdapter.java
  13. 110 0
      app/src/main/java/com/example/json/JsonFactory.java
  14. 179 0
      app/src/main/java/com/example/plugina/AEvent.java
  15. 35 0
      app/src/main/java/com/example/plugina/ActivityA.java
  16. 13 0
      app/src/main/java/com/example/plugina/ActivityB.java
  17. 5 0
      app/src/main/java/com/example/plugina/IEventListener.java
  18. 241 0
      app/src/main/java/com/example/plugina/Man.java
  19. 190 0
      app/src/main/java/com/example/plugina/PluginView.java
  20. 54 0
      app/src/main/java/com/example/plugina/ServiceA.java
  21. 6 0
      app/src/main/java/com/example/plugina/ServiceABinderInterface.java
  22. 551 0
      app/src/main/java/com/example/plugina/ViewPush.java
  23. 479 0
      app/src/main/java/com/example/plugina/ViewPush2.java
  24. 239 0
      app/src/main/java/com/example/utils/Utils.java
  25. BIN
      app/src/main/res/drawable/aaa.png
  26. 9 0
      app/src/main/res/drawable/bottom_border.xml
  27. BIN
      app/src/main/res/drawable/btn_close_normal.png
  28. BIN
      app/src/main/res/drawable/capture.png
  29. BIN
      app/src/main/res/drawable/close.png
  30. BIN
      app/src/main/res/drawable/dialog_ic_close_normal_holo_light.png
  31. BIN
      app/src/main/res/drawable/full_screen.png
  32. 170 0
      app/src/main/res/drawable/ic_launcher_background.xml
  33. 30 0
      app/src/main/res/drawable/ic_launcher_foreground.xml
  34. BIN
      app/src/main/res/drawable/left.png
  35. BIN
      app/src/main/res/drawable/login.jpg
  36. BIN
      app/src/main/res/drawable/microphone_off.png
  37. BIN
      app/src/main/res/drawable/microphone_on.png
  38. BIN
      app/src/main/res/drawable/normal_screen.png
  39. 7 0
      app/src/main/res/drawable/plugin_info_box.xml
  40. BIN
      app/src/main/res/drawable/switch_camera.png
  41. 18 0
      app/src/main/res/layout/activity_b.xml
  42. 41 0
      app/src/main/res/layout/activity_main.xml
  43. 24 0
      app/src/main/res/layout/activity_test.xml
  44. 19 0
      app/src/main/res/layout/content_main.xml
  45. 35 0
      app/src/main/res/layout/fragment_first.xml
  46. 35 0
      app/src/main/res/layout/fragment_second.xml
  47. 73 0
      app/src/main/res/layout/plugin_view.xml
  48. 84 0
      app/src/main/res/layout/view_push.xml
  49. 6 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
  50. 6 0
      app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
  51. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher.webp
  52. BIN
      app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
  53. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher.webp
  54. BIN
      app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
  55. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher.webp
  56. BIN
      app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
  57. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
  58. BIN
      app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
  59. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
  60. BIN
      app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
  61. 28 0
      app/src/main/res/navigation/nav_graph.xml
  62. 3 0
      app/src/main/res/values-land/dimens.xml
  63. 21 0
      app/src/main/res/values-night/themes.xml
  64. 9 0
      app/src/main/res/values-v23/themes.xml
  65. 3 0
      app/src/main/res/values-w1240dp/dimens.xml
  66. 3 0
      app/src/main/res/values-w600dp/dimens.xml
  67. 13 0
      app/src/main/res/values/colors.xml
  68. 7 0
      app/src/main/res/values/dimens.xml
  69. 45 0
      app/src/main/res/values/strings.xml
  70. 8 0
      app/src/main/res/values/styles.xml
  71. 21 0
      app/src/main/res/values/themes.xml
  72. 17 0
      app/src/test/java/com/example/plugina/ExampleUnitTest.java
  73. 4 0
      build.gradle
  74. 1 0
      cdv-gradle-config.json
  75. 26 0
      dist/CJINFO.xml
  76. BIN
      dist/apk/app-debug.apk
  77. 71 0
      dist/index.html
  78. 8 0
      dist/yhjk.svg
  79. 21 0
      gradle.properties
  80. 30 0
      gradle/libs.versions.toml
  81. BIN
      gradle/wrapper/gradle-wrapper.jar
  82. 6 0
      gradle/wrapper/gradle-wrapper.properties
  83. 185 0
      gradlew
  84. 89 0
      gradlew.bat
  85. 22 0
      repositories.gradle
  86. 25 0
      settings.gradle

+ 17 - 0
.gitignore

@@ -0,0 +1,17 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+
+.git

+ 1 - 0
app/.gitignore

@@ -0,0 +1 @@
+/build

+ 48 - 0
app/build.gradle

@@ -0,0 +1,48 @@
+plugins {
+    alias(libs.plugins.android.application)
+}
+
+android {
+    namespace 'com.example.plugina'
+    compileSdk 34
+
+    defaultConfig {
+        applicationId "com.example.plugina"
+        minSdk 24
+        targetSdk 34
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+        }
+    }
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_11
+        targetCompatibility JavaVersion.VERSION_11
+    }
+    buildFeatures {
+        viewBinding true
+    }
+}
+
+dependencies {
+    implementation files('libs/gson-2.8.2.jar')
+    implementation libs.constraintlayout
+    implementation libs.navigation.fragment
+    implementation libs.navigation.ui
+    implementation files('libs/librtmp.aar')
+    compileOnly files('libs/apkloaderbase-release.aar')
+    implementation files('libs/NodeMediaClient-Android-2.7.3.aar')
+
+    implementation libs.appcompat
+    implementation libs.material
+    testImplementation libs.junit
+    androidTestImplementation libs.ext.junit
+    androidTestImplementation libs.espresso.core
+}

BIN
app/libs/NodeMediaClient-Android-2.7.3.aar


BIN
app/libs/apkloaderbase-release.aar


BIN
app/libs/gson-2.8.2.jar


BIN
app/libs/librtmp.aar


+ 21 - 0
app/proguard-rules.pro

@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
app/src/androidTest/java/com/example/plugina/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package com.example.plugina;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assertEquals("com.example.plugina", appContext.getPackageName());
+    }
+}

+ 31 - 0
app/src/main/AndroidManifest.xml

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.plugina">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"></uses-permission>
+    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"></uses-permission>
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.PluginLoader">
+<!--        <activity-->
+<!--            android:name="com.example.MainActivity"-->
+<!--            android:exported="true"-->
+<!--            android:theme="@style/Theme.PluginLoader"-->
+<!--            android:screenOrientation="landscape">-->
+<!--            <intent-filter>-->
+<!--                <action android:name="android.intent.action.MAIN" />-->
+<!--                <category android:name="android.intent.category.LAUNCHER" />-->
+<!--            </intent-filter>-->
+<!--        </activity>-->
+        <activity android:name=".ActivityA" />
+        <activity android:name=".ActivityB" />
+
+        <service android:name=".ServiceA" />
+    </application>
+
+</manifest>

+ 221 - 0
app/src/main/java/com/example/MainActivity.java

@@ -0,0 +1,221 @@
+package com.example;
+
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+
+import com.example.plugina.PluginView;
+import com.google.android.material.snackbar.Snackbar;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.LinearInterpolator;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.navigation.NavController;
+import androidx.navigation.Navigation;
+import androidx.navigation.ui.AppBarConfiguration;
+import androidx.navigation.ui.NavigationUI;
+
+import com.example.plugina.databinding.ActivityMainBinding;
+
+import com.bingo.apkloader.base.ApkContext;
+import com.bingo.apkloader.base.ApkWindowBuild;
+
+import com.example.plugina.R;
+
+import java.util.Map;
+
+public class MainActivity extends AppCompatActivity implements View.OnClickListener {
+
+    private Button showButton;
+    private Button showButton2;
+    private Button hideButton;
+    PluginView pluginView;
+//    private LinearLayout pluginView;
+//    private TextView pluginTitle;
+//    private ViewGroup.LayoutParams windowParams;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_main);
+
+//        showButton = findViewById(R.id.show);
+//        showButton.setOnClickListener(this);
+//        showButton2 = findViewById(R.id.show2);
+//        showButton2.setOnClickListener(this);
+//        hideButton = findViewById(R.id.hide);
+//        hideButton.setOnClickListener(this);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            if (!Settings.canDrawOverlays(this)) {
+                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
+                        Uri.parse("package:" + getPackageName()));
+//        startActivityForResult(intent);
+                startActivity(intent);
+            }
+        }
+
+//        pluginView.show();
+//        pluginView = findViewById(R.id.plugin_view);
+//        pluginTitle = findViewById(R.id.plugin_title);
+//
+//        windowParams = pluginView.getLayoutParams();
+    }
+
+    @Override
+    public void onClick(View v) {
+//        if (v.getId() == R.id.show){
+//            WindowManager ap =  (WindowManager) this.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
+//            Map<String, Object> params = Map.of(
+//                    "width", "40%",
+//                    "height", 480,
+//                    "direction", "left", // "left", "right", "top", "bottom"
+//                    "title", "Plugin Title"
+//            );
+//            pluginView = new PluginView(this, ap, params);
+//            // 获取屏幕宽度
+//            pluginView.show();
+//        } else if (v.getId() == R.id.show2){
+//            WindowManager ap =  (WindowManager) this.getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
+//            Map<String, Object> params = Map.of(
+//                    "width", "40%",
+//                    "height", 480,
+//                    "direction", "right", // "left", "right", "top", "bottom"
+//                    "title", "Plugin Title"
+//            );
+//            pluginView = new PluginView(this, ap, params);
+//            // 获取屏幕宽度
+//            pluginView.show();
+//        } else {
+//            pluginView.hide();
+//        }
+    }
+
+
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        // Activity 可见时显示浮动窗口
+        if (pluginView != null) {
+            pluginView.show();
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        // Activity 不可见时隐藏浮动窗口
+        if (pluginView != null) {
+            pluginView.hide();
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        // 清理资源
+        if (pluginView != null) {
+            pluginView.hide();
+        }
+    }
+
+//    public void show(Map<String, Object> params) {
+//        parseParams(params);
+//
+////        TextView titleView = contentView.findViewById(R.id.plugin_title);
+////        titleView.setText(this.title);
+//
+//        if ("horizontal".equals(this.orientation)){
+//            int maxScreenWidth = windowManager.getDefaultDisplay().getWidth();
+//            windowParams.width = (int)(maxScreenWidth * 0.5);
+//            windowParams.x = windowParams.width;
+//        }else{
+//            int maxScreenHeight = windowManager.getDefaultDisplay().getHeight();
+//            windowParams.height = (int)(maxScreenHeight * 0.5);
+//            windowParams.y = windowParams.height;
+//        }
+//        windowParams.width = this.mWidth;
+//        windowParams.height = this.mHeight;
+//
+//        // 设置初始位置在右侧外部
+//        windowParams.x = windowManager.getDefaultDisplay().getWidth(); // 初始在右侧外面
+//        windowParams.y = 0;
+//
+//        Log.d(TAG, "-->show this.apkWindowBuild.AddView");
+//        this.apkWindowBuild.AddView(this.contentView, windowParams);
+//
+//
+//        // 设置动画(从右侧滑入)
+//        this.contentView.animate()
+//                .translationX(0)  // 动画移动到屏幕左侧
+//                .setDuration(300)
+//                .start();
+//    }
+//
+//    // 隐藏插件信息框
+//    public void hide() {
+//        if (this.contentView != null) {
+//            this.contentView.animate()
+//                    .translationX(this.contentView.getWidth())  // 滑回右侧
+//                    .setDuration(300)
+//                    .withEndAction(() -> {
+//                        windowManager.removeView(this.contentView);  // 从 WindowManager 移除
+//                        this.contentView = null;
+//                    })
+//                    .start();
+//        }
+//    }
+//
+//    private void init() {
+//        if (windowManager == null) {
+//            windowManager = (WindowManager) this.apkContext.getContext().getSystemService(Context.WINDOW_SERVICE);
+//        }
+//        if (windowParams == null) {
+//            // 初始化LayoutParams
+//            windowParams = new WindowManager.LayoutParams(
+//                    WindowManager.LayoutParams.MATCH_PARENT,
+//                    WindowManager.LayoutParams.MATCH_PARENT,
+//                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
+//                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+//                    PixelFormat.TRANSLUCENT
+//            );
+//        }
+//        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+//            windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+//        }else{
+//            windowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+//        }
+//        windowParams.gravity = Gravity.START | Gravity.TOP;
+//    }
+//
+//
+//    private void parseParams(Map<String, Object> params) {
+//        if (params != null) {
+//            this.mWidth = (int) params.getOrDefault("width", 640);
+//            this.mHeight = (int) params.getOrDefault("height", 480);
+//            this.orientation = (String) params.getOrDefault("orientation", "vertical");
+//            this.title = (String) params.getOrDefault("title", "");
+//        } else {
+//            this.mWidth = 640;
+//            this.mHeight = 480;
+//            this.orientation = "vertical"; // 默认竖屏
+//            this.title = ""; // 默认标题
+//        }
+//    }
+}

+ 39 - 0
app/src/main/java/com/example/json/JsonBooleanAsIntTypeAdapter.java

@@ -0,0 +1,39 @@
+package com.example.json;
+
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+
+public class JsonBooleanAsIntTypeAdapter extends TypeAdapter<Boolean> {
+    public JsonBooleanAsIntTypeAdapter() {
+    }
+
+    public void write(JsonWriter out, Boolean value) throws IOException {
+        if (value == null) {
+            out.nullValue();
+        } else {
+            out.value(value);
+        }
+
+    }
+
+    public Boolean read(JsonReader in) throws IOException {
+        JsonToken peek = in.peek();
+        switch(peek) {
+            case BOOLEAN:
+                return in.nextBoolean();
+            case NULL:
+                in.nextNull();
+                return null;
+            case NUMBER:
+                return in.nextInt() != 0;
+            case STRING:
+                return Boolean.parseBoolean(in.nextString());
+            default:
+                throw new IllegalStateException("Expected BOOLEAN or NUMBER but was " + peek);
+        }
+    }
+}

+ 110 - 0
app/src/main/java/com/example/json/JsonFactory.java

@@ -0,0 +1,110 @@
+package com.example.json;
+
+
+import com.example.utils.Utils;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
+import java.util.Date;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class JsonFactory {
+    private static Gson gson;
+    private static Gson getExposeGson;
+
+    public JsonFactory() {
+    }
+
+    public static Gson getGson() {
+        if (gson == null) {
+            gson = createGsonBuilder().create();
+        }
+
+        return gson;
+    }
+
+    public static Gson getExposeGson() {
+        if (getExposeGson == null) {
+            getExposeGson = createGsonBuilderWithExpose().create();
+        }
+
+        return getExposeGson;
+    }
+
+    public static GsonBuilder createGsonBuilder() {
+        JsonBooleanAsIntTypeAdapter booleanAsIntAdapter = new JsonBooleanAsIntTypeAdapter();
+        return (new GsonBuilder()).registerTypeAdapter(Boolean.class, booleanAsIntAdapter).registerTypeAdapter(Boolean.TYPE, booleanAsIntAdapter).excludeFieldsWithModifiers(new int[]{16, 128, 8}).registerTypeAdapter(Date.class, new JsonSerializer<Date>() {
+            public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
+                return new JsonPrimitive(src.getTime());
+            }
+        }).registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
+            public Date deserialize(JsonElement p1, Type p2, JsonDeserializationContext p3) {
+                JsonPrimitive jsonPrimitive = p1.getAsJsonPrimitive();
+                return jsonPrimitive.isNumber() ? new Date(jsonPrimitive.getAsLong()) : Utils.getDate(jsonPrimitive.getAsString());
+            }
+        }).registerTypeAdapter(Double.class, new JsonDeserializer<Double>() {
+            public Double deserialize(JsonElement p1, Type p2, JsonDeserializationContext p3) {
+                JsonPrimitive jsonPrimitive = p1.getAsJsonPrimitive();
+                return jsonPrimitive.isNumber() ? jsonPrimitive.getAsDouble() : 0.0D;
+            }
+        }).registerTypeAdapter(Long.TYPE, new JsonDeserializer<Long>() {
+            public Long deserialize(JsonElement p1, Type p2, JsonDeserializationContext p3) {
+                JsonPrimitive jsonPrimitive = p1.getAsJsonPrimitive();
+                return jsonPrimitive.isNumber() ? jsonPrimitive.getAsLong() : 0L;
+            }
+        }).registerTypeAdapter(Long.class, new JsonDeserializer<Long>() {
+            public Long deserialize(JsonElement p1, Type p2, JsonDeserializationContext p3) {
+                JsonPrimitive jsonPrimitive = p1.getAsJsonPrimitive();
+                return jsonPrimitive.isNumber() ? jsonPrimitive.getAsLong() : 0L;
+            }
+        }).registerTypeAdapter(JSONArray.class, new JsonDeserializer<JSONArray>() {
+            public JSONArray deserialize(JsonElement p1, Type p2, JsonDeserializationContext p3) {
+                JSONArray jsonArray = null;
+
+                try {
+                    if (p1.isJsonArray()) {
+                        jsonArray = new JSONArray(p1.getAsJsonArray().toString());
+                    } else {
+                        jsonArray = new JSONArray(p1.getAsString());
+                    }
+                } catch (Exception var6) {
+                    var6.printStackTrace();
+                }
+
+                return jsonArray;
+            }
+        }).registerTypeAdapter(JSONObject.class, new JsonDeserializer<JSONObject>() {
+            public JSONObject deserialize(JsonElement p1, Type p2, JsonDeserializationContext p3) {
+                JSONObject jsonObject = null;
+
+                try {
+                    if (p1.isJsonObject()) {
+                        jsonObject = new JSONObject(p1.getAsJsonObject().toString());
+                    } else {
+                        jsonObject = new JSONObject(p1.getAsString());
+                    }
+                } catch (Exception var6) {
+                    var6.printStackTrace();
+                }
+
+                return jsonObject;
+            }
+        }).registerTypeAdapter(Object.class, new JsonDeserializer<Object>() {
+            public Object deserialize(JsonElement p1, Type p2, JsonDeserializationContext p3) {
+                return p3.deserialize(p1, p2);
+            }
+        });
+    }
+
+    public static GsonBuilder createGsonBuilderWithExpose() {
+        return createGsonBuilder().excludeFieldsWithoutExposeAnnotation();
+    }
+}
+

+ 179 - 0
app/src/main/java/com/example/plugina/AEvent.java

@@ -0,0 +1,179 @@
+package com.example.plugina;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AEvent {
+    private static List<EventObj> callBackList = new ArrayList<EventObj>();
+
+    private static Handler mHandler;
+
+    public static void setHandler(Handler handler){
+        mHandler = handler;
+    }
+
+    public static void addListener(String eventID, IEventListener eventListener){
+        for(EventObj eventObj:callBackList){
+            if(eventObj.eventID.equals(eventID)&&eventObj.eventListener.getClass()==eventListener.getClass()) {
+                return;
+            }
+        }
+        EventObj event = new EventObj();
+        event.eventListener = eventListener;
+        event.eventID = eventID;
+        callBackList.add(event);
+    }
+
+    public static void removeListener(String eventID, IEventListener eventListener){
+        int i;
+        EventObj event;
+        for(i=0;i<callBackList.size();i++){
+            event = callBackList.get(i);
+            if(event.eventID.equals(eventID) && event.eventListener == eventListener){
+                callBackList.remove(i);
+                return;
+                //i--;
+            }
+        }
+    }
+
+    public static void notifyListener(final String eventID, final boolean success, final Object object){
+
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            // UI线程
+            int i;
+            EventObj event;
+            for(i=0;i<callBackList.size();i++){
+                event = callBackList.get(i);
+                if(event.eventID.equals(eventID)){
+                    event.eventListener.dispatchEvent(eventID,success,object);
+                }
+            }
+        } else {
+            // 非UI线程
+            if(mHandler!=null){
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        int i;
+                        EventObj event;
+                        for(i=0;i<callBackList.size();i++){
+                            event = callBackList.get(i);
+                            if(event.eventID.equals(eventID)){
+                                event.eventListener.dispatchEvent(eventID,success,object);
+                            }
+                        }
+                    }
+                });
+            }else{
+                int i;
+                EventObj event;
+                for(i=0;i<callBackList.size();i++){
+                    event = callBackList.get(i);
+                    if(event.eventID.equals(eventID)){
+                        event.eventListener.dispatchEvent(eventID,success,object);
+                    }
+                }
+            }
+        }
+
+    }
+
+    private static class EventObj{
+        IEventListener eventListener;
+        String eventID;
+    }
+
+    //事件类型在这里定义
+    public static final String AEVENT_LOGIN                     = "AEVENT_LOGIN";
+    public static final String AEVENT_LOGOUT                    = "AEVENT_LOGOUT";
+    public static final String AEVENT_RESET                     = "AEVENT_RESET";
+
+    public static final String AEVENT_GROUP_GOT_LIST            = "AEVENT_GROUP_GOT_LIST";
+    public static final String AEVENT_GROUP_GOT_MEMBER_LIST     = "AEVENT_GROUP_GOT_MEMBER_LIST";
+    public static final String AEVENT_GOT_ONLINE_USER_LIST      = "AEVENT_GOT_ONLINE_USER_LIST";
+
+    public static final String AEVENT_VOIP_INIT_COMPLETE        = "AEVENT_VOIP_INIT_COMPLETE";
+    public static final String AEVENT_VOIP_REV_CALLING          = "AEVENT_VOIP_REV_CALLING";
+    public static final String AEVENT_VOIP_REV_CALLING_AUDIO    = "AEVENT_VOIP_REV_CALLING_AUDIO";
+    public static final String AEVENT_VOIP_REV_REFUSED          = "AEVENT_VOIP_REV_REFUSED";
+    public static final String AEVENT_VOIP_REV_HANGUP           = "AEVENT_VOIP_REV_HANGUP";
+    public static final String AEVENT_VOIP_REV_BUSY             = "AEVENT_VOIP_REV_BUSY";
+    public static final String AEVENT_VOIP_REV_MISS             = "AEVENT_VOIP_REV_MISS";
+    public static final String AEVENT_VOIP_REV_CONNECT          = "AEVENT_VOIP_REV_CONNECT";
+    public static final String AEVENT_VOIP_REV_ERROR            = "AEVENT_VOIP_REV_ERROR";
+    public static final String AEVENT_VOIP_REV_REALTIME_DATA    = "AEVENT_VOIP_REV_REALTIME_DATA";
+    public static final String AEVENT_VOIP_P2P_REV_CALLING      = "AEVENT_VOIP_P2P_REV_CALLING";
+    public static final String AEVENT_VOIP_P2P_REV_CALLING_AUDIO= "AEVENT_VOIP_P2P_REV_CALLING_AUDIO";
+    public static final String AEVENT_VOIP_TRANS_STATE_CHANGED  = "AEVENT_VOIP_TRANS_STATE_CHANGED";
+
+    public static final String AEVENT_LIVE_ADD_UPLOADER         = "AEVENT_LIVE_ADD_UPLOADER";
+    public static final String AEVENT_LIVE_REMOVE_UPLOADER      = "AEVENT_LIVE_REMOVE_UPLOADER";
+    public static final String AEVENT_LIVE_ERROR                = "AEVENT_LIVE_ERROR";
+    public static final String AEVENT_LIVE_GET_ONLINE_NUMBER    = "AEVENT_LIVE_GET_ONLINE_NUMBER";
+    public static final String AEVENT_LIVE_SELF_KICKED          = "AEVENT_LIVE_SELF_KICKED";
+    public static final String AEVENT_LIVE_SELF_BANNED          = "AEVENT_LIVE_SELF_BANNED";
+    public static final String AEVENT_LIVE_REV_MSG              = "AEVENT_LIVE_REV_MSG";
+    public static final String AEVENT_LIVE_REV_PRIVATE_MSG      = "AEVENT_LIVE_REV_PRIVATE_MSG";
+    public static final String AEVENT_LIVE_APPLY_LINK           = "AEVENT_LIVE_APPLY_LINK";
+    public static final String AEVENT_LIVE_APPLY_LINK_RESULT    = "AEVENT_LIVE_APPLY_LINK_RESULT";
+    public static final String AEVENT_LIVE_INVITE_LINK           = "AEVENT_LIVE_INVITE_LINK";
+    public static final String AEVENT_LIVE_INVITE_LINK_RESULT    = "AEVENT_LIVE_INVITE_LINK_RESULT";
+    public static final String AEVENT_LIVE_SELF_COMMANDED_TO_STOP  = "AEVENT_LIVE_SELF_COMMANDED_TO_STOP";
+    public static final String AEVENT_LIVE_REV_REALTIME_DATA    = "AEVENT_LIVE_REV_REALTIME_DATA";
+    public static final String AEVENT_LIVE_PUSH_STREAM_ERROR    = "AEVENT_LIVE_PUSH_STREAM_ERROR";
+
+    public static final String AEVENT_SUPER_ROOM_ADD_UPLOADER         = "AEVENT_SUPER_ROOM_ADD_UPLOADER";
+    public static final String AEVENT_SUPER_ROOM_REMOVE_UPLOADER      = "AEVENT_SUPER_ROOM_REMOVE_UPLOADER";
+    public static final String AEVENT_SUPER_ROOM_ERROR                = "AEVENT_SUPER_ROOM_ERROR";
+    public static final String AEVENT_SUPER_ROOM_GET_ONLINE_NUMBER    = "AEVENT_SUPER_ROOM_GET_ONLINE_NUMBER";
+    public static final String AEVENT_SUPER_ROOM_SELF_KICKED          = "AEVENT_SUPER_ROOM_SELF_KICKED";
+    public static final String AEVENT_SUPER_ROOM_SELF_BANNED          = "AEVENT_SUPER_ROOM_SELF_BANNED";
+    public static final String AEVENT_SUPER_ROOM_REV_MSG              = "AEVENT_SUPER_ROOM_REV_MSG";
+    public static final String AEVENT_SUPER_ROOM_REV_PRIVATE_MSG      = "AEVENT_SUPER_ROOM_REV_PRIVATE_MSG";
+    public static final String AEVENT_SUPER_ROOM_APPLY_LINK           = "AEVENT_SUPER_ROOM_APPLY_LINK";
+    public static final String AEVENT_SUPER_ROOM_SELF_COMMANDED_TO_STOP  = "AEVENT_SUPER_ROOM_SELF_COMMANDED_TO_STOP";
+    public static final String AEVENT_SUPER_ROOM_REV_REALTIME_DATA    = "AEVENT_SUPER_ROOM_REV_REALTIME_DATA";
+    public static final String AEVENT_SUPER_ROOM_PUSH_STREAM_ERROR    = "AEVENT_SUPER_ROOM_PUSH_STREAM_ERROR";
+
+    public static final String AEVENT_MEETING_ADD_UPLOADER          = "AEVENT_MEETING_ADD_UPLOADER";
+    public static final String AEVENT_MEETING_REMOVE_UPLOADER       = "AEVENT_MEETING_REMOVE_UPLOADER";
+    public static final String AEVENT_MEETING_ERROR                 = "AEVENT_MEETING_ERROR";
+    public static final String AEVENT_MEETING_GET_ONLINE_NUMBER     = "AEVENT_MEETING_GET_ONLINE_NUMBER";
+    public static final String AEVENT_MEETING_SELF_KICKED           = "AEVENT_MEETING_SELF_KICKED";
+    public static final String AEVENT_MEETING_SELF_BANNED           = "AEVENT_MEETING_SELF_BANNED";
+    public static final String AEVENT_MEETING_REV_MSG               = "AEVENT_MEETING_REV_MSG";
+    public static final String AEVENT_MEETING_REV_PRIVATE_MSG       = "AEVENT_MEETING_REV_PRIVATE_MSG";
+    public static final String AEVENT_MEETING_REV_REALTIME_DATA     = "AEVENT_MEETING_REV_REALTIME_DATA";
+    public static final String AEVENT_MEETING_PUSH_STREAM_ERROR     = "AEVENT_MEETING_PUSH_STREAM_ERROR";
+
+    public static final String AEVENT_ECHO_FIN                  = "AEVENT_ECHO_FIN";
+
+    public static final String AEVENT_CHATROOM_ERROR            ="AEVENT_CHATROOM_ERROR";
+    public static final String AEVENT_CHATROOM_STOP_OK          ="AEVENT_CHATROOM_STOP_OK";
+    public static final String AEVENT_CHATROOM_DELETE_OK        ="AEVENT_CHATROOM_DELETE_OK";
+    public static final String AEVENT_CHATROOM_SELF_BANNED      ="AEVENT_CHATROOM_SELF_BANNED";
+    public static final String AEVENT_CHATROOM_SELF_KICKED      ="AEVENT_CHATROOM_SELF_KICKED";
+    public static final String AEVENT_CHATROOM_REV_MSG          ="AEVENT_CHATROOM_REV_MSG";
+    public static final String AEVENT_CHATROOM_REV_PRIVATE_MSG  ="AEVENT_CHATROOM_REV_PRIVATE_MSG";
+    public static final String AEVENT_CHATROOM_GET_ONLINE_NUMBER="AEVENT_CHATROOM_GET_ONLINE_NUMBER";
+
+    public static final String AEVENT_C2C_REV_MSG               ="AEVENT_C2C_REV_MSG";
+    public static final String AEVENT_GROUP_REV_MSG             ="AEVENT_GROUP_REV_MSG";
+    public static final String AEVENT_REV_SYSTEM_MSG             ="AEVENT_REV_SYSTEM_MSG";
+
+    public static final String AEVENT_CONN_DEATH               ="AEVENT_CONN_DEATH";
+    public static final String AEVENT_USER_KICKED               ="AEVENT_USER_KICKED";
+    public static final String AEVENT_USER_ONLINE               ="AEVENT_USER_ONLINE";
+    public static final String AEVENT_USER_OFFLINE              ="AEVENT_USER_OFFLINE";
+
+    public static final String AEVENT_RTSP_FORWARD              ="AEVENT_RTSP_FORWARD";
+    public static final String AEVENT_RTSP_STOP                 ="AEVENT_RTSP_STOP";
+    public static final String AEVENT_RTSP_RESUME               ="AEVENT_RTSP_RESUME";
+    public static final String AEVENT_RTSP_DELETE               ="AEVENT_RTSP_DELETE";
+
+    public static final String AEVENT_GOT_LIST                  ="AEVENT_GOT_LIST";
+}

+ 35 - 0
app/src/main/java/com/example/plugina/ActivityA.java

@@ -0,0 +1,35 @@
+package com.example.plugina;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import com.bingo.apkloader.base.ApkBaseActivity;
+import com.bingo.apkloader.base.ApkIntent;
+
+public class ActivityA extends ApkBaseActivity implements View.OnClickListener {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        this.setContentView(R.layout.activity_test);
+
+        Button btnStartActivityB = (Button) this.findViewById(R.id.btnStartActivityB);
+        btnStartActivityB.setOnClickListener(this);
+        Button btnStartActivityC = (Button) this.findViewById(R.id.btnStartActivityC);
+        btnStartActivityC.setOnClickListener(this);
+    }
+
+    @Override
+    public void onClick(View v) {
+        ApkIntent apkIntent = new ApkIntent();
+        apkIntent.setPackageName("com.example.plugina");
+        if (v.getId() == R.id.btnStartActivityB){
+            apkIntent.setClassName(ActivityB.class);
+            this.startActivity(apkIntent);
+        }
+//        else if (v.getId() == R.id.btnStartActivityC){
+//            apkIntent.setClassName(ActivityC.class);
+//            this.startActivity(apkIntent);
+//        }
+    }
+}

+ 13 - 0
app/src/main/java/com/example/plugina/ActivityB.java

@@ -0,0 +1,13 @@
+package com.example.plugina;
+
+import android.os.Bundle;
+
+import com.bingo.apkloader.base.ApkBaseActivity;
+
+public class ActivityB extends ApkBaseActivity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        this.setContentView(R.layout.activity_b);
+    }
+}

+ 5 - 0
app/src/main/java/com/example/plugina/IEventListener.java

@@ -0,0 +1,5 @@
+package com.example.plugina;
+
+public interface IEventListener {
+    void dispatchEvent(String aEventID, boolean success, Object eventObj);
+}

+ 241 - 0
app/src/main/java/com/example/plugina/Man.java

@@ -0,0 +1,241 @@
+package com.example.plugina;
+
+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.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.DocumentsContract;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.bingo.apkloader.base.Apk;
+import com.bingo.apkloader.base.ApkConstants;
+import com.bingo.apkloader.base.ApkContext;
+import com.bingo.apkloader.base.ApkJsInterface;
+import com.bingo.apkloader.base.ApkOnActivityResultCallback;
+import com.bingo.apkloader.base.ApkWindowBuild;
+import com.example.json.JsonFactory;
+
+import java.util.Map;
+
+public class Man implements Apk, ApkJsInterface, ApkOnActivityResultCallback {
+    private final static String TAG = "plugina.Man";
+
+    private ApkContext apkContext = null;
+    private int testRequestCode = 1001;
+    private ServiceConnection serviceConnection;
+    private ServiceABinderInterface serviceABinderInterface;
+    private final String testAction = "com.bingo.test.apk";
+    private BroadcastReceiver broadcastReceiver = null;
+    public ApkWindowBuild apkWindowBuilder = null;
+    PluginView pluginView = null;
+
+    public static String stringFromJNI(){
+        return "hello from java";
+    }
+
+    @Override
+    public void onCreate(ApkContext apkContext) {
+        this.apkContext = apkContext;
+        this.apkContext.setJsInterface(this);
+        this.apkContext.setPluginOnActivityResultCallback(this);
+    }
+
+    @Override
+    public void onCreated() {
+        Log.d(TAG, "onCreated");
+
+        // 启动service的需要放到onResume中,onCreate中实际还未初始完,所以无法启动
+        this.apkContext.startService("com.example.plugina.ServiceA");
+
+        this.apkContext.postDelay(new Runnable() {
+            @Override
+            public void run() {
+                Intent intent = new Intent();
+                intent.setAction(testAction);
+                intent.putExtra("from", TAG);
+                Man.this.apkContext.sendBroadcast(intent);
+            }
+        }, 10000);
+    }
+
+    @Override
+    public void onResume() {
+        // onResume
+        Log.d(TAG, "onResume--->" + broadcastReceiver);
+        if (broadcastReceiver == null){
+            broadcastReceiver = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (intent.getAction().equals(testAction)){
+                        Log.e(TAG, "-------->" + testAction + " receiver" + " from = " + intent.getStringExtra("from"));
+                    }
+                }
+            };
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(testAction);
+            this.apkContext.registerReceiver(broadcastReceiver, intentFilter);
+        }
+        if (pluginView != null){
+            pluginView.show();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        // onStop
+        Log.d(TAG, "onStop");
+        if (pluginView != null){
+            pluginView.hide();
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        // 释放资源
+        Log.d(TAG, "释放资源---->" + broadcastReceiver);
+        if (broadcastReceiver != null){
+            BroadcastReceiver broadcastReceiverTemp = broadcastReceiver;
+            broadcastReceiver = null;
+            this.apkContext.unregisterReceiver(broadcastReceiverTemp);
+        }
+        System.gc();
+        if (pluginView != null) {
+            pluginView.hide();
+        }
+    }
+
+    @Override
+    public void onHandle(String action, Object o) {
+        if (ApkConstants.ApkHandleAction.ActionCreateWindowBuilder.equals(action)){
+            this.apkWindowBuilder = (ApkWindowBuild) o;
+        }
+    }
+
+    @Override
+    public void call(String action, Map<String, Object> params) {
+        Log.d(TAG, "call action = " + action + "-->");
+        if ("sayHello".equals(action)){
+            Log.d(TAG, "call call sayHello");
+            this.sayHello(params);
+        }else if ("startPicActivity".equals(action)){
+            Log.d(TAG, "call call startPicActivity");
+            this.startPicActivity();
+        }else if ("bindService".equals(action)){
+            bindService(params);
+        }else if ("unbindService".equals(action)){
+            unbindService();
+        }else if ("startActivity".equals(action)){
+            startActivity();
+        }else if ("initView".equals(action)){
+            addView();
+        } else if("test".equals(action)) {
+            if (pluginView != null) {
+                pluginView.hide();
+            }
+            pluginView = new PluginView(this.apkContext, this.apkWindowBuilder, params);
+            pluginView.show();
+            ViewPush2 viewPush = new ViewPush2(this.apkWindowBuilder, "rtmp://46.109.1.100:1935", "https://46.109.1.100:18080", "userid", "token", "man", "group", "userid", "vertical");
+            View view = viewPush.initView();
+            pluginView.updatePluginView(view);
+        }else {
+            addView();
+        }
+    }
+
+    @Override
+    public void onPluginActivityResult(int requestCode, int resultCode, Intent data) {
+        if (requestCode == this.testRequestCode){
+            if (resultCode == -1){
+                if (data != null && data.getData() != null){
+                    Log.d(TAG, "get data and show toast");
+                    this.apkContext.showToast(data.getDataString(), 0);
+                    return;
+                }
+            }
+        }
+        Log.d(TAG, "can not get data");
+    }
+
+    private void addView(){
+        Log.d(TAG, "call addView ViewPush");
+        // 参数pushUrl指定对的推送url
+        ViewPush viewPush = new ViewPush(this.apkContext, this.apkWindowBuilder, "rtmp://46.109.1.100:1935", "https://46.109.1.100:18080", "userid", "token", "man", "group", "userid", "vertical");
+        viewPush.show();
+    }
+
+    private void startActivity(){
+        this.apkContext.startActivity("com.example.plugina.ActivityA");
+    }
+
+    public void bindService(Map<String, Object> params){
+        final int a = Integer.parseInt(params.get("a").toString());
+        final int b = Integer.parseInt(params.get("b").toString());
+        if (serviceConnection == null) {
+            serviceConnection = new ServiceConnection() {
+                public void onServiceDisconnected(ComponentName name) {
+                }
+                public void onServiceConnected(ComponentName name, IBinder binder) {
+                    serviceABinderInterface = (ServiceABinderInterface)binder;
+                    int sum = serviceABinderInterface.sum(a, b);
+                    Log.e(TAG, "onServiceConnected 1 sum = " + sum);
+                    apkContext.showToast("sum = " + sum + "--" + serviceABinderInterface.hello(), Toast.LENGTH_SHORT);
+                }
+            };
+            this.apkContext.bindService("com.example.plugina.ServiceA", serviceConnection, Context.BIND_AUTO_CREATE);
+        }else{
+            if (serviceABinderInterface != null){
+                int sum = serviceABinderInterface.sum(a, b);
+                Log.d(TAG, "onServiceConnected 2 sum = " + sum);
+                apkContext.showToast("sum = " + sum + "--" + serviceABinderInterface.hello(), Toast.LENGTH_SHORT);
+            }
+        }
+    }
+
+    public void unbindService(){
+        if (serviceConnection != null) {
+            this.apkContext.unbindService("com.example.plugina.ServiceA", serviceConnection);
+            serviceConnection = null;
+        }
+    }
+
+    public void sayHello(Map<String, Object> params){
+        Map<String, Object>unitInfo = new ArrayMap<>();
+        unitInfo.put("name", "cetc28s");
+        unitInfo.put("address", "北京路39号");
+        String jsonStr = JsonFactory.getGson().toJson(unitInfo);
+        Log.d(TAG, "unit info: " + jsonStr);
+
+        String msg = "Hello from Man and hello from js name = " + params.get("name") + " and " + stringFromJNI();
+        Log.d(TAG, msg);
+        Map<String, Object>echoParams = new ArrayMap<>();
+        echoParams.put("msg", msg);
+        Log.d(TAG, "sayHello echo sayNativeHelloEcho");
+        this.apkContext.post(new Runnable() {
+            @Override
+            public void run() {
+                Man.this.apkContext.echo("sayNativeHelloEcho", echoParams);
+            }
+        });
+    }
+
+    public void startPicActivity(){
+        String path = "%2f";
+        Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary:" + path);
+        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType("*/*");
+        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);
+
+        this.apkContext.startActivityForResult(intent, testRequestCode, null);
+    }
+}

+ 190 - 0
app/src/main/java/com/example/plugina/PluginView.java

@@ -0,0 +1,190 @@
+package com.example.plugina;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.hardware.Camera;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.LinearInterpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.bingo.apkloader.base.ApkContext;
+import com.bingo.apkloader.base.ApkWindowBuild;
+import com.github.faucamp.simplertmp.RtmpHandler;
+
+import net.ossrs.yasea.SrsCameraView;
+import net.ossrs.yasea.SrsEncodeHandler;
+import net.ossrs.yasea.SrsPublisher;
+import net.ossrs.yasea.SrsRecordHandler;
+
+import java.util.Map;
+
+public class PluginView implements View.OnClickListener {
+    private static final String TAG = "PluginView";
+
+    private ApkWindowBuild apkWindowBuild;
+    private ApkContext apkContext;
+//    private Context context;
+    private WindowManager windowManager;
+    private WindowManager.LayoutParams windowParams;
+
+    private String title = "";
+    private int mWidth = 640;
+    private String direction = "left"; // 默认右侧
+
+    private View pluginView = null;
+    private FrameLayout pluginContent = null;
+    private ImageView btnClose = null;
+    private ImageView btnBack = null;
+
+    public PluginView(ApkContext apkContext, ApkWindowBuild apkWindowBuild, Map<String, Object> params) {
+        this.apkContext = apkContext;
+        this.apkWindowBuild = apkWindowBuild;
+
+        this.pluginView = this.apkWindowBuild.Inflate(R.layout.plugin_view);
+
+        this.init(params);
+    }
+
+//    public PluginView(Context context, WindowManager windowManager, Map<String, Object> params) {
+//        this.context = context;
+//        this.windowManager = windowManager;
+//
+//        // 通过 LayoutInflater 加载你的布局文件
+//        LayoutInflater inflater = LayoutInflater.from(context);
+//        this.pluginView = inflater.inflate(R.layout.plugin_view, null);
+//
+//        this.init(params);
+//    }
+
+    public void show() {
+        Log.d(TAG, "-->show this.apkWindowBuild.AddView");
+        if (this.pluginView.getParent() != null) {
+            this.apkWindowBuild.updateViewLayout(pluginView, windowParams);
+        } else {
+            this.apkWindowBuild.AddView(this.pluginView, windowParams);
+        }
+    }
+
+    public void updatePluginView(View view) {
+        this.pluginContent.removeAllViews();
+        this.pluginContent.addView(view);
+    }
+
+    // 隐藏插件信息框
+    public void hide() {
+        // 检查pluginView是否已经被移除
+        if (this.pluginView.getParent() != null) {
+            this.apkWindowBuild.removeView(this.pluginView);
+        } else {
+            Log.d(TAG, "--> pluginView is already removed, skipping remove operation.");
+        }
+    }
+
+    private void init(Map<String, Object> params) {
+        pluginContent = pluginView.findViewById(R.id.plugin_content);
+        btnClose = pluginView.findViewById(R.id.plugin_close_btn);
+        btnClose.setOnClickListener(this);
+        btnBack = pluginView.findViewById(R.id.plugin_back_btn);
+        btnBack.setOnClickListener(this);
+        TextView titleView = pluginView.findViewById(R.id.plugin_title);
+
+        if (windowManager == null) {
+            windowManager = (WindowManager) this.apkContext.getContext().getSystemService(Context.WINDOW_SERVICE);
+        }
+        if (windowParams == null) {
+            // 初始化LayoutParams
+            windowParams = new WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.WRAP_CONTENT,
+                    WindowManager.LayoutParams.WRAP_CONTENT,
+                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                    PixelFormat.TRANSLUCENT
+            );
+        }
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+            windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+        }else{
+            windowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        }
+
+        int screenWidth = windowManager.getDefaultDisplay().getWidth();
+        int screenHeight = windowManager.getDefaultDisplay().getHeight();
+        int defaultWidth = (int) (screenWidth * 0.4);
+        if (params != null) {
+            this.title = (String) params.getOrDefault("title", "");
+            this.mWidth = parseSize(params.get("width"), screenWidth, defaultWidth);
+            this.direction = (String) params.getOrDefault("direction", "left");
+        } else {
+            this.title = ""; // 默认标题
+            this.mWidth = defaultWidth;
+            this.direction = "left"; // 默认竖屏
+        }
+        this.title += "a";
+
+        windowParams.x = 200;  // 设置x坐标,距离屏幕
+        windowParams.y = 0; // 设置y坐标,距离屏幕顶部15px
+        windowParams.width = this.mWidth;  // 设置宽度
+        windowParams.height = screenHeight;  // 设置高度,减去顶部和底部的间距
+
+        if (this.direction.equals("left")) {
+            windowParams.gravity = Gravity.START | Gravity.TOP;  // 使用 LEFT
+        } else if (this.direction.equals("right")) {
+            windowParams.gravity = Gravity.END | Gravity.TOP;  // 使用 RIGHT
+        }
+//        windowParams.x = 0; // 设置x坐标,距离屏幕左边0px
+
+        titleView.setText(this.title);
+    }
+
+    // 获取状态栏高度
+    private int getStatusBarHeight() {
+        int statusBarHeight = 0;
+        Resources resources = this.apkContext.getContext().getResources();
+        int resourceId = resources.getIdentifier("status_bar_height", "dimen", "android");
+
+        if (resourceId > 0) {
+            statusBarHeight = resources.getDimensionPixelSize(resourceId);
+        }
+
+        return statusBarHeight;
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getId() == R.id.plugin_back_btn){
+//            this.hide();
+        } else if (v.getId() == R.id.plugin_close_btn) {
+            this.hide();
+        }
+    }
+
+    private int parseSize(Object value, int base, int defaultValue) {
+        if (value == null) return defaultValue;
+        String str = value.toString().trim();
+        try {
+            if (str.endsWith("%")) {
+                float percent = Float.parseFloat(str.replace("%", "")) / 100f;
+                return (int) (base * percent);
+            } else {
+                return Integer.parseInt(str);
+            }
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+}

+ 54 - 0
app/src/main/java/com/example/plugina/ServiceA.java

@@ -0,0 +1,54 @@
+package com.example.plugina;
+
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import com.bingo.apkloader.base.ApkBaseService;
+
+public class ServiceA extends ApkBaseService {
+    private final static String TAG = "ServiceA";
+    private ServiceABinder serviceABinder = new ServiceABinder();
+
+    @Override
+    public void onCreate() {
+        // TODO Auto-generated method stub
+        Log.e(TAG, "onCreate " + this.toString());
+        super.onCreate();
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        // TODO Auto-generated method stub
+        Log.e(TAG, "onStartCommand " + this.toString());
+        return super.onStartCommand(intent, flags, startId);
+    }
+    @Override
+    public IBinder onBind(Intent intent) {
+        // TODO Auto-generated method stub
+        Log.e(TAG, "onBind");
+        return serviceABinder;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        // TODO Auto-generated method stub
+        Log.e(TAG, "onUnbind");
+        return super.onUnbind(intent);
+    }
+
+
+    private class ServiceABinder extends Binder implements ServiceABinderInterface{
+        @Override
+        public int sum(int a, int b) {
+            // TODO Auto-generated method stub
+            return a + b;
+        }
+
+        @Override
+        public String hello() {
+            return Man.stringFromJNI();
+        }
+    }
+}

+ 6 - 0
app/src/main/java/com/example/plugina/ServiceABinderInterface.java

@@ -0,0 +1,6 @@
+package com.example.plugina;
+
+public interface ServiceABinderInterface {
+    int sum(int a, int b);
+    String hello();
+}

+ 551 - 0
app/src/main/java/com/example/plugina/ViewPush.java

@@ -0,0 +1,551 @@
+package com.example.plugina;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.PixelFormat;
+import android.hardware.Camera;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.bingo.apkloader.base.ApkContext;
+import com.bingo.apkloader.base.ApkWindowBuild;
+import com.github.faucamp.simplertmp.RtmpHandler;
+
+import net.ossrs.yasea.SrsCameraView;
+import net.ossrs.yasea.SrsEncodeHandler;
+import net.ossrs.yasea.SrsPublisher;
+import net.ossrs.yasea.SrsRecordHandler;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.SocketException;
+
+public class ViewPush implements
+        RtmpHandler.RtmpListener, SrsRecordHandler.SrsRecordListener, SrsEncodeHandler.SrsEncodeListener,
+        SurfaceHolder.Callback, View.OnTouchListener, View.OnClickListener {
+    private static final String TAG = "ViewPush";
+
+    private ApkWindowBuild apkWindowBuild;
+    private ApkContext apkContext;
+    private WindowManager windowManager;
+    private WindowManager.LayoutParams windowParams;
+
+    private String pushUrl;
+    private String apiBaseUrl;
+    private String userId;
+    private String token;
+    private String sourceType;
+    private String group;
+    private String owner;
+    private String orientation;
+
+    private boolean destroyed = false;
+    private int mWidth = 640;
+    private int mHeight = 480;
+    private static final long DOUBLE_BACK_TOUCH_INTERVAL = 2000; // 两次按下返回键的时间间隔,单位为毫秒
+    private long mLastBackPressTime = 0;
+
+    private SrsPublisher mPublisher;
+    private SrsCameraView mCameraView;
+    private TextView txtOnlineCount;
+
+    private View contentView = null;
+    private Handler mainLooperHandler = new Handler(Looper.getMainLooper());
+
+    public ViewPush(ApkContext apkContext, ApkWindowBuild apkWindowBuild,String pushUrl,
+                    String apiBaseUrl,
+                    String userId,
+                    String token,
+                    String sourceType,
+                    String group,
+                    String owner,
+                    String orientation) {
+        this.pushUrl = pushUrl;
+        this.apiBaseUrl = apiBaseUrl;
+        this.userId = userId;
+        this.token = token;
+        this.sourceType = sourceType;
+        this.group = group;
+        this.owner = owner;
+        this.orientation = orientation;
+
+        this.apkContext = apkContext;
+        this.apkWindowBuild = apkWindowBuild;
+        this.contentView = this.apkWindowBuild.Inflate(R.layout.view_push);
+        this.init();
+    }
+
+    public void show(){
+        mCameraView = this.contentView.findViewById(R.id.glsurfaceview_camera);
+        mCameraView.setInitPreviewOrientation("horizontal".equals(this.orientation) ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT);
+        mCameraView.setOnTouchListener(this);
+        mCameraView.setCameraCallbacksHandler(new SrsCameraView.CameraCallbacksHandler(){
+            @Override
+            public void onCameraParameters(Camera.Parameters params) {
+            }
+        });
+
+        mPublisher = new SrsPublisher(mCameraView);
+        mPublisher.setEncodeHandler(new SrsEncodeHandler(this));
+        mPublisher.setRtmpHandler(new RtmpHandler(this));
+        mPublisher.setRecordHandler(new SrsRecordHandler(this));
+        mPublisher.setPreviewResolution(mWidth, mHeight);
+        mPublisher.setOutputResolution(mHeight, mWidth); // 这里要和preview反过来
+        mPublisher.setVideoHDMode();
+
+
+        this.contentView.findViewById(R.id.ivClose).setOnClickListener(this);
+        this.contentView.findViewById(R.id.btnSwitch).setOnClickListener(this);
+        this.contentView.findViewById(R.id.btnEnableAudio).setOnClickListener(this);
+        this.contentView.findViewById(R.id.btnCapture).setOnClickListener(this);
+        this.contentView.findViewById(R.id.ivFullscreen).setOnClickListener(this);
+
+        handler.post(httpRequestRunnable);
+        mainLooperHandler.post(new Runnable() {
+            @Override
+            public void run() {
+//                startPush();
+            }
+        });
+
+        if (windowManager == null) {
+            windowManager = (WindowManager) this.apkContext.getContext().getSystemService(Context.WINDOW_SERVICE);
+        }
+
+        if (windowParams == null) {
+            int LAYOUT_FLAG;
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+                LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+            } else {
+                LAYOUT_FLAG = WindowManager.LayoutParams.TYPE_PHONE;
+            }
+            // 初始化LayoutParams
+            windowParams = new WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.MATCH_PARENT,
+                    WindowManager.LayoutParams.MATCH_PARENT,
+                    WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+                    PixelFormat.TRANSLUCENT
+            );
+        }
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+            windowParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+        }else{
+            windowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        }
+        windowParams.gravity = Gravity.START | Gravity.TOP;
+
+        if ("horizontal".equals(this.orientation)){
+            int maxScreenWidth = windowManager.getDefaultDisplay().getWidth();
+            windowParams.width = (int)(maxScreenWidth * 0.5);
+            windowParams.x = windowParams.width;
+        }else{
+            int maxScreenHeight = windowManager.getDefaultDisplay().getHeight();
+            windowParams.height = (int)(maxScreenHeight * 0.5);
+            windowParams.y = windowParams.height;
+        }
+        Log.d(TAG, "-->show this.apkWindowBuild.AddView");
+        this.apkWindowBuild.AddView(this.contentView, windowParams);
+    }
+
+    private void init() {
+        txtOnlineCount = this.contentView.findViewById(R.id.txtOnlineCount);
+    }
+
+    private void startPush() {
+        mPublisher.startCamera();
+        mPublisher.startPublish(this.pushUrl);
+    }
+
+    ////////////////////////////////////////////get和set///////////////////////////////////////////////
+    public static String getTAG() {
+        return TAG;
+    }
+
+    public ApkWindowBuild getApkWindowBuild() {
+        return apkWindowBuild;
+    }
+
+    public void setApkViewBuild(ApkWindowBuild apkWindowBuild) {
+        this.apkWindowBuild = apkWindowBuild;
+    }
+
+    public String getPushUrl() {
+        return pushUrl;
+    }
+
+    public void setPushUrl(String pushUrl) {
+        this.pushUrl = pushUrl;
+    }
+
+    public String getApiBaseUrl() {
+        return apiBaseUrl;
+    }
+
+    public void setApiBaseUrl(String apiBaseUrl) {
+        this.apiBaseUrl = apiBaseUrl;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getSourceType() {
+        return sourceType;
+    }
+
+    public void setSourceType(String sourceType) {
+        this.sourceType = sourceType;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public String getOwner() {
+        return owner;
+    }
+
+    public void setOwner(String owner) {
+        this.owner = owner;
+    }
+
+    private void close(){
+        this.destroyed = true;
+
+        mPublisher.stopPublish();
+        mPublisher.stopRecord();
+
+//        if (Main.liveBroadcastId != null && !Main.liveBroadcastId.isEmpty()){
+//            new Thread(new Runnable() {
+//                @Override
+//                public void run() {
+//                    HttpUtil.shutdownLiveBroadcast(ViewPush.this.apiBaseUrl, ViewPush.this.token, Main.liveBroadcastId);
+//                }
+//            }).start();
+//        }
+
+        this.apkWindowBuild.removeView(this.contentView);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getId() == R.id.ivClose){
+            long currentTime = System.currentTimeMillis();
+            if (currentTime - mLastBackPressTime > DOUBLE_BACK_TOUCH_INTERVAL) {
+                mLastBackPressTime = currentTime;
+                Toast.makeText(this.contentView.getContext(), "再按一次退出直播", Toast.LENGTH_SHORT).show();
+            } else {
+                this.close();
+            }
+        }else if (v.getId() == R.id.btnSwitch){
+            mPublisher.switchCameraFace((mPublisher.getCameraId() + 1) % Camera.getNumberOfCameras());
+        }else if(v.getId() == R.id.btnEnableAudio){
+            if (v.getTag() == null){
+                mPublisher.setSendVideoOnly(true);
+                ((ImageView)v).setImageResource(R.drawable.microphone_off);
+                v.setTag(1);
+            }else{
+                mPublisher.setSendVideoOnly(false);
+                ((ImageView)v).setImageResource(R.drawable.microphone_on);
+                v.setTag(null);
+            }
+        }else if(v.getId() == R.id.btnCapture){
+            takePicture();
+        }else if(v.getId() == R.id.ivFullscreen){
+            Object tag = v.getTag();
+            if (tag == null || tag.toString() == "full_screen"){
+                this.updateContentViewFullscreen(true);
+                ((ImageView)v).setImageResource(R.drawable.normal_screen);
+                v.setTag("normal_screen");
+            }else{
+                this.updateContentViewFullscreen(false);
+                ((ImageView)v).setImageResource(R.drawable.full_screen);
+                v.setTag("full_screen");
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        int action = event.getAction();
+        if (event.getPointerCount() > 1) {
+            if (action == MotionEvent.ACTION_MOVE) {
+                mCameraView.setZoom(event);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 设置是否为全屏
+     * @param full
+     */
+    private void updateContentViewFullscreen(boolean full){
+        if (full){
+            if ("horizontal".equals(this.orientation)){
+                this.windowParams.width = WindowManager.LayoutParams.MATCH_PARENT;
+            }else{
+                this.windowParams.height = WindowManager.LayoutParams.MATCH_PARENT;
+            }
+        }else{
+            if ("horizontal".equals(this.orientation)){
+                int maxScreenWidth = windowManager.getDefaultDisplay().getWidth();
+                this.windowParams.width = (int)(maxScreenWidth * 0.5);
+                this.windowParams.x = windowParams.width;
+            }else{
+                int maxScreenHeight = windowManager.getDefaultDisplay().getHeight();
+                this.windowParams.height = (int)(maxScreenHeight * 0.5);
+                this.windowParams.y = windowParams.height;
+            }
+        }
+////        this.apkWindowBuild.updateViewLayout(this.contentView, this.windowParams);
+    }
+
+    // 截图方法
+    private void takePicture() {
+        if(this.mCameraView.getCamera() != null) {
+            this.mCameraView.getCamera().takePicture(null, null, new Camera.PictureCallback() {
+                @Override
+                public void onPictureTaken(byte[] data, Camera camera) {
+                    // data就是图片的字节数组
+                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                    // 保存图片
+                    saveBitmap(bitmap);
+                    // 重新开始预览
+                    camera.startPreview();
+                }
+            });
+        }
+    }
+
+    private void saveBitmap(Bitmap bitmap) {
+        String fileName = "IMG_" + System.currentTimeMillis() + ".jpg";
+        File file = new File(this.contentView.getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);
+
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
+            fos.flush();
+        } catch (IOException e) {
+            Log.e(TAG, e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
+    private Handler handler = new Handler();
+    private Runnable httpRequestRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (ViewPush.this.destroyed){
+                Log.d(TAG, "exit request task");
+                return;
+            }
+            updateTextViewContent();
+            // 每隔3秒发起一次HTTP请求
+            handler.postDelayed(this, 3000);
+        }
+    };
+
+    private void updateTextViewContent() {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+//                    if (Main.liveBroadcastId.isEmpty()){
+//                        return;
+//                    }
+                    final long count = 0; //HttpUtil.fetchOnlineCount(ViewPush.this.apiBaseUrl, ViewPush.this.token, Main.liveBroadcastId, "push", "");
+
+                    // 在UI线程更新TextView内容
+                    ViewPush.this.contentView.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            ViewPush.this.txtOnlineCount.setText("在线观看人数:" + count);
+                        }
+                    });
+                } catch (Exception e) {
+                    Log.e(TAG, e.getMessage());
+                }
+            }
+        }).start();
+    }
+
+    private String getCreateJsonString() {
+        String formatStr = "{\"sourceType\":\"%s\", \"objectId\":\"%s\", \"config\":\"{}\", \"owner\":\"%s\", \"group\":\"%s\", \"status\":%d}";
+        return String.format(formatStr, this.sourceType, this.userId, this.owner, this.group.trim().isEmpty() ? "72-0" : this.group.trim(), 10);
+    }
+
+    @Override
+    public void onRtmpConnecting(String s) {
+        Toast.makeText(ViewPush.this.contentView.getContext(), "连接中", Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    public void onRtmpConnected(String s) {
+        mainLooperHandler.post(new Runnable() {
+            @Override
+            public void run() {
+//                // 空的则代表需要创建直播
+//                new Thread(new Runnable() {
+//                    @Override
+//                    public void run() {
+//                        if (ViewPush.this.destroyed){
+//                            return;
+//                        }
+//                        if (Main.liveBroadcastId.isEmpty()) {
+//                            Main.liveBroadcastId = HttpUtil.createLiveBroadcase(ViewPush.this.apiBaseUrl, ViewPush.this.token, ViewPush.this.getCreateJsonString());
+//                        }else{
+//                            HttpUtil.openLiveBroadcast(ViewPush.this.apiBaseUrl, ViewPush.this.token, Main.liveBroadcastId);
+//                        }
+//                    }
+//                }).start();
+                Toast.makeText(ViewPush.this.contentView.getContext(), "连接成功", Toast.LENGTH_SHORT).show();
+            }
+        });
+    }
+
+    @Override
+    public void onRtmpVideoStreaming() {
+
+    }
+
+    @Override
+    public void onRtmpAudioStreaming() {
+
+    }
+
+    @Override
+    public void onRtmpStopped() {
+
+    }
+
+    @Override
+    public void onRtmpDisconnected() {
+
+    }
+
+    @Override
+    public void onRtmpVideoFpsChanged(double v) {
+
+    }
+
+    @Override
+    public void onRtmpVideoBitrateChanged(double v) {
+
+    }
+
+    @Override
+    public void onRtmpAudioBitrateChanged(double v) {
+
+    }
+
+    @Override
+    public void onRtmpSocketException(SocketException e) {
+
+    }
+
+    @Override
+    public void onRtmpIOException(IOException e) {
+
+    }
+
+    @Override
+    public void onRtmpIllegalArgumentException(IllegalArgumentException e) {
+
+    }
+
+    @Override
+    public void onRtmpIllegalStateException(IllegalStateException e) {
+
+    }
+
+    @Override
+    public void onNetworkWeak() {
+
+    }
+
+    @Override
+    public void onNetworkResume() {
+
+    }
+
+    @Override
+    public void onEncodeIllegalArgumentException(IllegalArgumentException e) {
+
+    }
+
+    @Override
+    public void onRecordPause() {
+
+    }
+
+    @Override
+    public void onRecordResume() {
+
+    }
+
+    @Override
+    public void onRecordStarted(String s) {
+
+    }
+
+    @Override
+    public void onRecordFinished(String s) {
+
+    }
+
+    @Override
+    public void onRecordIllegalArgumentException(IllegalArgumentException e) {
+
+    }
+
+    @Override
+    public void onRecordIOException(IOException e) {
+
+    }
+}

+ 479 - 0
app/src/main/java/com/example/plugina/ViewPush2.java

@@ -0,0 +1,479 @@
+package com.example.plugina;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.PixelFormat;
+import android.hardware.Camera;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.bingo.apkloader.base.ApkContext;
+import com.bingo.apkloader.base.ApkWindowBuild;
+import com.github.faucamp.simplertmp.RtmpHandler;
+
+import net.ossrs.yasea.SrsCameraView;
+import net.ossrs.yasea.SrsEncodeHandler;
+import net.ossrs.yasea.SrsPublisher;
+import net.ossrs.yasea.SrsRecordHandler;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.SocketException;
+
+public class ViewPush2 implements
+        RtmpHandler.RtmpListener, SrsRecordHandler.SrsRecordListener, SrsEncodeHandler.SrsEncodeListener,
+        SurfaceHolder.Callback, View.OnTouchListener, View.OnClickListener {
+    private static final String TAG = "ViewPush";
+
+    private String pushUrl;
+    private String apiBaseUrl;
+    private String userId;
+    private String token;
+    private String sourceType;
+    private String group;
+    private String owner;
+    private String orientation;
+
+    private boolean destroyed = false;
+    private int mWidth = 640;
+    private int mHeight = 480;
+    private static final long DOUBLE_BACK_TOUCH_INTERVAL = 2000; // 两次按下返回键的时间间隔,单位为毫秒
+    private long mLastBackPressTime = 0;
+
+    private SrsPublisher mPublisher;
+    private SrsCameraView mCameraView;
+    private TextView txtOnlineCount;
+
+    private View contentView = null;
+    private Handler mainLooperHandler = new Handler(Looper.getMainLooper());
+
+    public ViewPush2(ApkWindowBuild apkWindowBuild, String pushUrl,
+                     String apiBaseUrl,
+                     String userId,
+                     String token,
+                     String sourceType,
+                     String group,
+                     String owner,
+                     String orientation) {
+        this.pushUrl = pushUrl;
+        this.apiBaseUrl = apiBaseUrl;
+        this.userId = userId;
+        this.token = token;
+        this.sourceType = sourceType;
+        this.group = group;
+        this.owner = owner;
+        this.orientation = orientation;
+
+        this.contentView = apkWindowBuild.Inflate(R.layout.view_push);
+        this.init();
+    }
+
+    public View initView(){
+        mCameraView = this.contentView.findViewById(R.id.glsurfaceview_camera);
+        mCameraView.setInitPreviewOrientation("horizontal".equals(this.orientation) ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT);
+        mCameraView.setOnTouchListener(this);
+        mCameraView.setCameraCallbacksHandler(new SrsCameraView.CameraCallbacksHandler(){
+            @Override
+            public void onCameraParameters(Camera.Parameters params) {
+            }
+        });
+
+        mPublisher = new SrsPublisher(mCameraView);
+        mPublisher.setEncodeHandler(new SrsEncodeHandler(this));
+        mPublisher.setRtmpHandler(new RtmpHandler(this));
+        mPublisher.setRecordHandler(new SrsRecordHandler(this));
+        mPublisher.setPreviewResolution(mWidth, mHeight);
+        mPublisher.setOutputResolution(mHeight, mWidth); // 这里要和preview反过来
+        mPublisher.setVideoHDMode();
+
+
+        this.contentView.findViewById(R.id.ivClose).setOnClickListener(this);
+        this.contentView.findViewById(R.id.btnSwitch).setOnClickListener(this);
+        this.contentView.findViewById(R.id.btnEnableAudio).setOnClickListener(this);
+        this.contentView.findViewById(R.id.btnCapture).setOnClickListener(this);
+        this.contentView.findViewById(R.id.ivFullscreen).setOnClickListener(this);
+
+        handler.post(httpRequestRunnable);
+        mainLooperHandler.post(new Runnable() {
+            @Override
+            public void run() {
+//                startPush();
+            }
+        });
+
+        return this.contentView;
+    }
+
+    private void init() {
+        txtOnlineCount = this.contentView.findViewById(R.id.txtOnlineCount);
+    }
+
+    private void startPush() {
+        mPublisher.startCamera();
+        mPublisher.startPublish(this.pushUrl);
+    }
+
+    ////////////////////////////////////////////get和set///////////////////////////////////////////////
+    public static String getTAG() {
+        return TAG;
+    }
+
+    public String getPushUrl() {
+        return pushUrl;
+    }
+
+    public void setPushUrl(String pushUrl) {
+        this.pushUrl = pushUrl;
+    }
+
+    public String getApiBaseUrl() {
+        return apiBaseUrl;
+    }
+
+    public void setApiBaseUrl(String apiBaseUrl) {
+        this.apiBaseUrl = apiBaseUrl;
+    }
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public void setToken(String token) {
+        this.token = token;
+    }
+
+    public String getSourceType() {
+        return sourceType;
+    }
+
+    public void setSourceType(String sourceType) {
+        this.sourceType = sourceType;
+    }
+
+    public String getGroup() {
+        return group;
+    }
+
+    public void setGroup(String group) {
+        this.group = group;
+    }
+
+    public String getOwner() {
+        return owner;
+    }
+
+    public void setOwner(String owner) {
+        this.owner = owner;
+    }
+
+    private void close(){
+        this.destroyed = true;
+
+        mPublisher.stopPublish();
+        mPublisher.stopRecord();
+
+//        if (Main.liveBroadcastId != null && !Main.liveBroadcastId.isEmpty()){
+//            new Thread(new Runnable() {
+//                @Override
+//                public void run() {
+//                    HttpUtil.shutdownLiveBroadcast(ViewPush.this.apiBaseUrl, ViewPush.this.token, Main.liveBroadcastId);
+//                }
+//            }).start();
+//        }
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+    }
+
+    @Override
+    public void onClick(View v) {
+        if (v.getId() == R.id.ivClose){
+            long currentTime = System.currentTimeMillis();
+            if (currentTime - mLastBackPressTime > DOUBLE_BACK_TOUCH_INTERVAL) {
+                mLastBackPressTime = currentTime;
+                Toast.makeText(this.contentView.getContext(), "再按一次退出直播", Toast.LENGTH_SHORT).show();
+            } else {
+                this.close();
+            }
+        }else if (v.getId() == R.id.btnSwitch){
+            mPublisher.switchCameraFace((mPublisher.getCameraId() + 1) % Camera.getNumberOfCameras());
+        }else if(v.getId() == R.id.btnEnableAudio){
+            if (v.getTag() == null){
+                mPublisher.setSendVideoOnly(true);
+                ((ImageView)v).setImageResource(R.drawable.microphone_off);
+                v.setTag(1);
+            }else{
+                mPublisher.setSendVideoOnly(false);
+                ((ImageView)v).setImageResource(R.drawable.microphone_on);
+                v.setTag(null);
+            }
+        }else if(v.getId() == R.id.btnCapture){
+            takePicture();
+        }else if(v.getId() == R.id.ivFullscreen){
+            Object tag = v.getTag();
+            if (tag == null || tag.toString() == "full_screen"){
+                this.updateContentViewFullscreen(true);
+                ((ImageView)v).setImageResource(R.drawable.normal_screen);
+                v.setTag("normal_screen");
+            }else{
+                this.updateContentViewFullscreen(false);
+                ((ImageView)v).setImageResource(R.drawable.full_screen);
+                v.setTag("full_screen");
+            }
+        }
+    }
+
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        int action = event.getAction();
+        if (event.getPointerCount() > 1) {
+            if (action == MotionEvent.ACTION_MOVE) {
+                mCameraView.setZoom(event);
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 设置是否为全屏
+     * @param full
+     */
+    private void updateContentViewFullscreen(boolean full){
+////        this.apkWindowBuild.updateViewLayout(this.contentView, this.windowParams);
+    }
+
+    // 截图方法
+    private void takePicture() {
+        if(this.mCameraView.getCamera() != null) {
+            this.mCameraView.getCamera().takePicture(null, null, new Camera.PictureCallback() {
+                @Override
+                public void onPictureTaken(byte[] data, Camera camera) {
+                    // data就是图片的字节数组
+                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                    // 保存图片
+                    saveBitmap(bitmap);
+                    // 重新开始预览
+                    camera.startPreview();
+                }
+            });
+        }
+    }
+
+    private void saveBitmap(Bitmap bitmap) {
+        String fileName = "IMG_" + System.currentTimeMillis() + ".jpg";
+        File file = new File(this.contentView.getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName);
+
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
+            fos.flush();
+        } catch (IOException e) {
+            Log.e(TAG, e.getMessage());
+            e.printStackTrace();
+        }
+    }
+
+    private Handler handler = new Handler();
+    private Runnable httpRequestRunnable = new Runnable() {
+        @Override
+        public void run() {
+            if (ViewPush2.this.destroyed){
+                Log.d(TAG, "exit request task");
+                return;
+            }
+            updateTextViewContent();
+            // 每隔3秒发起一次HTTP请求
+            handler.postDelayed(this, 3000);
+        }
+    };
+
+    private void updateTextViewContent() {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+//                    if (Main.liveBroadcastId.isEmpty()){
+//                        return;
+//                    }
+                    final long count = 0; //HttpUtil.fetchOnlineCount(ViewPush.this.apiBaseUrl, ViewPush.this.token, Main.liveBroadcastId, "push", "");
+
+                    // 在UI线程更新TextView内容
+                    ViewPush2.this.contentView.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            ViewPush2.this.txtOnlineCount.setText("在线观看人数:" + count);
+                        }
+                    });
+                } catch (Exception e) {
+                    Log.e(TAG, e.getMessage());
+                }
+            }
+        }).start();
+    }
+
+    private String getCreateJsonString() {
+        String formatStr = "{\"sourceType\":\"%s\", \"objectId\":\"%s\", \"config\":\"{}\", \"owner\":\"%s\", \"group\":\"%s\", \"status\":%d}";
+        return String.format(formatStr, this.sourceType, this.userId, this.owner, this.group.trim().isEmpty() ? "72-0" : this.group.trim(), 10);
+    }
+
+    @Override
+    public void onRtmpConnecting(String s) {
+        Toast.makeText(ViewPush2.this.contentView.getContext(), "连接中", Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    public void onRtmpConnected(String s) {
+        mainLooperHandler.post(new Runnable() {
+            @Override
+            public void run() {
+//                // 空的则代表需要创建直播
+//                new Thread(new Runnable() {
+//                    @Override
+//                    public void run() {
+//                        if (ViewPush.this.destroyed){
+//                            return;
+//                        }
+//                        if (Main.liveBroadcastId.isEmpty()) {
+//                            Main.liveBroadcastId = HttpUtil.createLiveBroadcase(ViewPush.this.apiBaseUrl, ViewPush.this.token, ViewPush.this.getCreateJsonString());
+//                        }else{
+//                            HttpUtil.openLiveBroadcast(ViewPush.this.apiBaseUrl, ViewPush.this.token, Main.liveBroadcastId);
+//                        }
+//                    }
+//                }).start();
+                Toast.makeText(ViewPush2.this.contentView.getContext(), "连接成功", Toast.LENGTH_SHORT).show();
+            }
+        });
+    }
+
+    @Override
+    public void onRtmpVideoStreaming() {
+
+    }
+
+    @Override
+    public void onRtmpAudioStreaming() {
+
+    }
+
+    @Override
+    public void onRtmpStopped() {
+
+    }
+
+    @Override
+    public void onRtmpDisconnected() {
+
+    }
+
+    @Override
+    public void onRtmpVideoFpsChanged(double v) {
+
+    }
+
+    @Override
+    public void onRtmpVideoBitrateChanged(double v) {
+
+    }
+
+    @Override
+    public void onRtmpAudioBitrateChanged(double v) {
+
+    }
+
+    @Override
+    public void onRtmpSocketException(SocketException e) {
+
+    }
+
+    @Override
+    public void onRtmpIOException(IOException e) {
+
+    }
+
+    @Override
+    public void onRtmpIllegalArgumentException(IllegalArgumentException e) {
+
+    }
+
+    @Override
+    public void onRtmpIllegalStateException(IllegalStateException e) {
+
+    }
+
+    @Override
+    public void onNetworkWeak() {
+
+    }
+
+    @Override
+    public void onNetworkResume() {
+
+    }
+
+    @Override
+    public void onEncodeIllegalArgumentException(IllegalArgumentException e) {
+
+    }
+
+    @Override
+    public void onRecordPause() {
+
+    }
+
+    @Override
+    public void onRecordResume() {
+
+    }
+
+    @Override
+    public void onRecordStarted(String s) {
+
+    }
+
+    @Override
+    public void onRecordFinished(String s) {
+
+    }
+
+    @Override
+    public void onRecordIllegalArgumentException(IllegalArgumentException e) {
+
+    }
+
+    @Override
+    public void onRecordIOException(IOException e) {
+
+    }
+}

+ 239 - 0
app/src/main/java/com/example/utils/Utils.java

@@ -0,0 +1,239 @@
+package com.example.utils;
+
+import org.json.JSONObject;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Map;
+
+public class Utils {
+    /**
+     * 判断字符串是否为空或null
+     * @param txt
+     *          字符串
+     * @return
+     *          true 空或null, false 非空
+     */
+    public static boolean isEmpty(String txt){
+        if (null == txt){
+            return true;
+        }
+        return "".equals(txt.trim());
+    }
+
+    /**
+     * 获取文件扩展名
+     * @param name
+     *          文件名
+     * @return
+     *          扩展名
+     */
+    public static String getFileExt(String name, boolean... withPoint){
+        if (isEmpty(name)){
+            return "";
+        }
+        int i = name.lastIndexOf(".");
+        if (i > 0){
+            if (withPoint == null || withPoint.length == 0 || !withPoint[0]){
+                return name.substring(i + 1);
+            }else{
+                return name.substring(i);
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 创建文件夹
+     * @param path
+     *          文件夹路径
+     * @return
+     *          true 成功;false 失败
+     */
+    public static boolean mkdirs(String path){
+        File f = new File(path);
+        if (!f.exists()){
+            f.mkdirs();
+        }
+        return true;
+    }
+
+    public static SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    public static SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
+    public static SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd HH:mm");
+    public static SimpleDateFormat sdf4 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
+    public static SimpleDateFormat sdfGMT;
+
+    public static <V> V getMapValue(Map map, Object key, V defaultValue) {
+        if (map == null) {
+            return defaultValue;
+        } else {
+            return map.containsKey(key) ? (V)map.get(key) : defaultValue;
+        }
+    }
+
+    public static String toString(Object obj) {
+        return obj != null && obj != JSONObject.NULL ? obj.toString() : "";
+    }
+
+    public static Boolean getBoolean(Object obj) {
+        return getBoolean(obj, false);
+    }
+
+    public static Boolean getBoolean(Object obj, Boolean defaultValue) {
+        Boolean result = defaultValue;
+        Object tmp = obj;
+        if (obj != null) {
+            try {
+                result = (Boolean)tmp;
+            } catch (Exception var5) {
+                tmp = obj.toString().toLowerCase();
+                result = tmp.equals("1") || tmp.equals("true");
+            }
+        }
+
+        return result;
+    }
+
+    public static Date getDate(Object obj) {
+        return getDate(obj, (Date)null);
+    }
+
+    public static Date getDate(Object obj, Date defaultValue) {
+        Date result = defaultValue;
+        Object tmp = obj;
+        if (obj != null) {
+            if (obj instanceof Date) {
+                result = (Date)obj;
+            } else if (obj instanceof Long) {
+                try {
+                    result = new Date((Long)tmp);
+                } catch (Exception var17) {
+                }
+            } else if (obj instanceof String) {
+                String tmpString = (String)obj;
+
+                try {
+                    result = new Date(Long.parseLong(tmpString));
+                } catch (Exception var16) {
+                    try {
+                        result = sdf1.parse(tmpString);
+                    } catch (Exception var15) {
+                        try {
+                            result = sdfGMT.parse(tmpString);
+                        } catch (Exception var14) {
+                            try {
+                                result = sdf3.parse(tmpString);
+                            } catch (Exception var13) {
+                                try {
+                                    result = sdf4.parse(tmpString);
+                                } catch (Exception var12) {
+                                    try {
+                                        result = sdf2.parse(tmpString);
+                                    } catch (Exception var11) {
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+    public static Integer getInteger(Object obj) {
+        return getInteger(obj, (Integer)null);
+    }
+
+    public static Integer getInteger(Object obj, Integer defaultValue) {
+        Object tmp = obj;
+        Integer result;
+        if (obj instanceof Integer) {
+            result = (Integer)obj;
+        } else {
+            result = defaultValue;
+
+            try {
+                String tmpString = tmp.toString();
+                result = Integer.parseInt(tmpString);
+            } catch (Exception var5) {
+            }
+        }
+
+        return result;
+    }
+
+    public static Long getLong(Object obj) {
+        return getLong(obj, (Long)null);
+    }
+
+    public static Long getLong(Object obj, Long defaultValue) {
+        Object tmp = obj;
+        Long result;
+        if (obj instanceof Long) {
+            result = (Long)obj;
+        } else {
+            result = defaultValue;
+
+            try {
+                String tmpString = tmp.toString();
+                result = Long.parseLong(tmpString);
+            } catch (Exception var5) {
+            }
+        }
+
+        return result;
+    }
+
+    public static Double getDouble(Object obj) {
+        return getDouble(obj, (Double)null);
+    }
+
+    public static Double getDouble(Object obj, Double defaultValue) {
+        Object tmp = obj;
+        Double result;
+        if (obj instanceof Double) {
+            result = (Double)obj;
+        } else {
+            result = defaultValue;
+
+            try {
+                String tmpString = tmp.toString();
+                result = Double.parseDouble(tmpString);
+            } catch (Exception var5) {
+            }
+        }
+
+        return result;
+    }
+
+    public static Float getFloat(Object obj) {
+        return getFloat(obj, (Float)null);
+    }
+
+    public static Float getFloat(Object obj, Float defaultValue) {
+        Object tmp = obj;
+        Float result;
+        if (obj instanceof Float) {
+            result = (Float)obj;
+        } else {
+            result = defaultValue;
+
+            try {
+                String tmpString = tmp.toString();
+                result = Float.parseFloat(tmpString);
+            } catch (Exception var5) {
+            }
+        }
+
+        return result;
+    }
+
+    static {
+        sdfGMT = new SimpleDateFormat("EEE MMM dd hh:mm:ss z yyyy", Locale.ENGLISH);
+    }
+}

BIN
app/src/main/res/drawable/aaa.png


+ 9 - 0
app/src/main/res/drawable/bottom_border.xml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape>
+            <corners android:topLeftRadius="0dp" android:topRightRadius="0dp"/>
+            <solid android:color="#43000000"/>
+        </shape>
+    </item>
+</layer-list>

BIN
app/src/main/res/drawable/btn_close_normal.png


BIN
app/src/main/res/drawable/capture.png


BIN
app/src/main/res/drawable/close.png


BIN
app/src/main/res/drawable/dialog_ic_close_normal_holo_light.png


BIN
app/src/main/res/drawable/full_screen.png


+ 170 - 0
app/src/main/res/drawable/ic_launcher_background.xml

@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#3DDC84"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>

+ 30 - 0
app/src/main/res/drawable/ic_launcher_foreground.xml

@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="85.84757"
+                android:endY="92.4963"
+                android:startX="42.9492"
+                android:startY="49.59793"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>

BIN
app/src/main/res/drawable/left.png


BIN
app/src/main/res/drawable/login.jpg


BIN
app/src/main/res/drawable/microphone_off.png


BIN
app/src/main/res/drawable/microphone_on.png


BIN
app/src/main/res/drawable/normal_screen.png


+ 7 - 0
app/src/main/res/drawable/plugin_info_box.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <solid android:color="#70000000"/>
+    <corners android:radius="0dp"/>
+<!--    <stroke android:width="1dp" android:color="#FFFFFF"/>-->
+</shape>

BIN
app/src/main/res/drawable/switch_camera.png


+ 18 - 0
app/src/main/res/layout/activity_b.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <TextView
+        android:id="@+id/tips"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_marginLeft="20dp"
+        android:text="我是PluginA中的ActivityB..."
+        android:textSize="25dp" />
+    <ImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="@drawable/aaa"></ImageView>
+</LinearLayout>

+ 41 - 0
app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:fitsSystemWindows="true"
+    tools:context="com.example.MainActivity"
+    android:background="@drawable/login">
+
+<!--    <LinearLayout-->
+<!--        android:layout_width="wrap_content"-->
+<!--        android:layout_height="wrap_content">-->
+
+<!--        <Button-->
+<!--            android:id="@+id/show"-->
+<!--            android:layout_width="wrap_content"-->
+<!--            android:layout_height="wrap_content"-->
+<!--            android:text="显示">-->
+<!--        </Button>    <Button-->
+<!--        android:id="@+id/show2"-->
+<!--        android:layout_width="wrap_content"-->
+<!--        android:layout_height="wrap_content"-->
+<!--        android:text="显示2">-->
+<!--    </Button>-->
+<!--        <Button-->
+<!--            android:id="@+id/hide"-->
+<!--            android:layout_width="wrap_content"-->
+<!--            android:layout_height="wrap_content"-->
+<!--            android:text="隐藏">-->
+<!--        </Button>-->
+
+<!--    </LinearLayout>-->
+
+    <include
+        android:id="@+id/plugin_view"
+        layout="@layout/plugin_view">
+    </include>
+
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 24 - 0
app/src/main/res/layout/activity_test.xml

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <TextView
+        android:id="@+id/tips"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        android:layout_marginLeft="20dp"
+        android:text="我是PluginA中的ActivityA..."
+        android:textSize="25dp" />
+    <Button
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/btnStartActivityB"
+        android:text="启动ActivityB"></Button>
+    <Button
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/btnStartActivityC"
+        android:text="启动ActivityC"></Button>
+</LinearLayout>

+ 19 - 0
app/src/main/res/layout/content_main.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+    <fragment
+        android:id="@+id/nav_host_fragment_content_main"
+        android:name="androidx.navigation.fragment.NavHostFragment"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:defaultNavHost="true"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:navGraph="@navigation/nav_graph" />
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 35 - 0
app/src/main/res/layout/fragment_first.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".FirstFragment">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:padding="16dp">
+
+        <Button
+            android:id="@+id/button_first"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/next"
+            app:layout_constraintBottom_toTopOf="@id/textview_first"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <TextView
+            android:id="@+id/textview_first"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:text="@string/lorem_ipsum"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/button_first" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.core.widget.NestedScrollView>

+ 35 - 0
app/src/main/res/layout/fragment_second.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".SecondFragment">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:padding="16dp">
+
+        <Button
+            android:id="@+id/button_second"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/previous"
+            app:layout_constraintBottom_toTopOf="@id/textview_second"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent" />
+
+        <TextView
+            android:id="@+id/textview_second"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:text="@string/lorem_ipsum"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/button_second" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.core.widget.NestedScrollView>

+ 73 - 0
app/src/main/res/layout/plugin_view.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/plugin_info_box"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:layout_marginVertical="0dp"
+    android:layout_marginHorizontal="0dp"
+    android:background="@drawable/plugin_info_box"
+    android:elevation="8dp">
+
+    <LinearLayout
+        android:id="@+id/header"
+        android:layout_width="match_parent"
+        android:layout_height="50dp"
+        android:paddingVertical="6dp"
+        android:paddingHorizontal="8dp"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:background="@drawable/bottom_border">
+
+        <android.widget.ImageView
+            android:id="@+id/plugin_back_btn"
+            android:layout_width="26dp"
+            android:layout_height="26dp"
+            android:src="@drawable/left"
+            ></android.widget.ImageView>
+
+        <android.widget.TextView
+            android:id="@+id/plugin_title"
+            android:layout_width="0dp"
+            android:layout_weight="1"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="12dp"
+            android:text="插件标题"
+            android:textSize="16sp"
+            android:gravity="center_vertical"
+            android:textColor="@color/white"
+            ></android.widget.TextView>
+
+        <android.widget.ImageView
+            android:id="@+id/plugin_close_btn"
+            android:layout_width="26dp"
+            android:layout_height="26dp"
+            android:src="@drawable/close"
+            ></android.widget.ImageView>
+
+    </LinearLayout>
+
+    <!-- Content -->
+    <android.widget.ScrollView
+        android:id="@+id/plugin_scroll_content"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:paddingTop="6dp"
+        android:background="@color/white" >
+
+        <FrameLayout
+            android:id="@+id/plugin_content"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <include
+                android:id="@+id/activity_example_rtmp"
+                layout="@layout/view_push">
+            </include>
+        </FrameLayout>
+
+    </android.widget.ScrollView>
+
+
+
+</LinearLayout>

+ 84 - 0
app/src/main/res/layout/view_push.xml

@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.widget.RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/activity_example_rtmp"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+        <net.ossrs.yasea.SrsCameraView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:id="@+id/glsurfaceview_camera" />
+        <android.widget.ImageView
+            android:id="@+id/ivClose"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:src="@drawable/close"
+            android:layout_alignParentRight="true"
+            android:layout_marginTop="10dp"
+            android:layout_marginRight="10dp"
+            ></android.widget.ImageView>
+        <android.widget.ImageView
+            android:id="@+id/ivFullscreen"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:src="@drawable/full_screen"
+            android:layout_marginTop="10dp"
+            android:layout_marginRight="20dp"
+            android:layout_toLeftOf="@+id/ivClose"
+            android:visibility="gone"
+            ></android.widget.ImageView>
+        <android.widget.TextView
+            android:id="@+id/txtOnlineCount"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_marginTop="14dp"
+            android:layout_marginLeft="10dp"
+            android:textColor="#ffffff"
+            android:text="在线观看人数:0"
+            ></android.widget.TextView>
+        <android.widget.TextView
+            android:id="@+id/txtTip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:layout_marginTop="14dp"
+            android:layout_marginLeft="10dp"
+            android:textColor="#ffffff"
+            android:text="在线观看人数:0"
+            android:visibility="gone"
+            ></android.widget.TextView>
+        <android.widget.ImageView
+            android:id="@+id/btnSwitch"
+            android:src="@drawable/switch_camera"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_alignParentRight="true"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="10dp"
+            android:layout_marginRight="10dp"
+            android:text="切换"></android.widget.ImageView>
+        <android.widget.ImageView
+            android:id="@+id/btnEnableAudio"
+            android:src="@drawable/microphone_on"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_alignTop="@+id/btnSwitch"
+            android:layout_toLeftOf="@+id/btnSwitch"
+            android:layout_marginBottom="10dp"
+            android:layout_marginRight="20dp"
+            android:gravity="center"
+            android:scaleType="centerInside"
+            android:text="禁音"></android.widget.ImageView>
+        <android.widget.ImageView
+            android:id="@+id/btnCapture"
+            android:src="@drawable/capture"
+            android:layout_width="32dp"
+            android:layout_height="32dp"
+            android:layout_alignTop="@+id/btnSwitch"
+            android:layout_toLeftOf="@+id/btnEnableAudio"
+            android:layout_marginBottom="10dp"
+            android:layout_marginRight="20dp"
+            android:text="截屏"
+            android:visibility="gone"></android.widget.ImageView>
+</android.widget.RelativeLayout>

+ 6 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

+ 6 - 0
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+    <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>

BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp


BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp


+ 28 - 0
app/src/main/res/navigation/nav_graph.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/nav_graph"
+    app:startDestination="@id/FirstFragment">
+
+    <fragment
+        android:id="@+id/FirstFragment"
+        android:name="com.example.FirstFragment"
+        android:label="@string/first_fragment_label"
+        tools:layout="@layout/fragment_first">
+
+        <action
+            android:id="@+id/action_FirstFragment_to_SecondFragment"
+            app:destination="@id/SecondFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/SecondFragment"
+        android:name="com.example.SecondFragment"
+        android:label="@string/second_fragment_label"
+        tools:layout="@layout/fragment_second">
+
+        <action
+            android:id="@+id/action_SecondFragment_to_FirstFragment"
+            app:destination="@id/FirstFragment" />
+    </fragment>
+</navigation>

+ 3 - 0
app/src/main/res/values-land/dimens.xml

@@ -0,0 +1,3 @@
+<resources>
+    <dimen name="fab_margin">48dp</dimen>
+</resources>

+ 21 - 0
app/src/main/res/values-night/themes.xml

@@ -0,0 +1,21 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.PluginLoader" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_200</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/black</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_200</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+    <!-- Base application theme. -->
+    <style name="Base.Theme.PluginLoader" parent="Theme.Material3.DayNight.NoActionBar">
+        <!-- Customize your dark theme here. -->
+        <!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
+    </style>
+</resources>

+ 9 - 0
app/src/main/res/values-v23/themes.xml

@@ -0,0 +1,9 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+
+    <style name="Theme.PluginLoader" parent="Base.Theme.PluginLoader">
+        <!-- Transparent system bars for edge-to-edge. -->
+        <item name="android:navigationBarColor">@android:color/transparent</item>
+        <item name="android:statusBarColor">@android:color/transparent</item>
+        <item name="android:windowLightStatusBar">?attr/isLightTheme</item>
+    </style>
+</resources>

+ 3 - 0
app/src/main/res/values-w1240dp/dimens.xml

@@ -0,0 +1,3 @@
+<resources>
+    <dimen name="fab_margin">200dp</dimen>
+</resources>

+ 3 - 0
app/src/main/res/values-w600dp/dimens.xml

@@ -0,0 +1,3 @@
+<resources>
+    <dimen name="fab_margin">48dp</dimen>
+</resources>

+ 13 - 0
app/src/main/res/values/colors.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+    <color name="plugin_bg">#F8F8F8</color>
+    <color name="close_red">#FF4D4D</color>
+    <color name="black2">#222222</color>
+</resources>

+ 7 - 0
app/src/main/res/values/dimens.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="plugin_padding">16dp</dimen>
+    <dimen name="plugin_corner_radius">12dp</dimen>
+    <dimen name="plugin_header_height">48dp</dimen>
+    <dimen name="fab_margin">16dp</dimen>
+</resources>

+ 45 - 0
app/src/main/res/values/strings.xml

@@ -0,0 +1,45 @@
+<resources>
+    <string name="app_name">Plugina</string>
+    <!-- Strings used for fragments for navigation -->
+    <string name="first_fragment_label">First Fragment</string>
+    <string name="second_fragment_label">Second Fragment</string>
+    <string name="next">Next</string>
+    <string name="previous">Previous</string>
+
+    <string name="lorem_ipsum">
+        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
+        volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
+        dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
+        litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend
+        diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a,
+        ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n
+        Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus
+        egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed
+        neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada
+        fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae,
+        molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor
+        bibendum, vel congue leo egestas.\n\n
+        Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit
+        amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel,
+        molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer
+        interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at
+        lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula,
+        in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
+        est.\n\n
+        Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh.
+        Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui
+        non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In
+        eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc,
+        quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra
+        ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a
+        placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus
+        convallis.\n\n
+        Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et
+        malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa
+        gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper,
+        libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper
+        sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
+        libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
+        vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
+    </string>
+</resources>

+ 8 - 0
app/src/main/res/values/styles.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="PluginInfoBoxTheme" parent="Theme.AppCompat.Light.NoActionBar">
+        <item name="android:background">@color/plugin_bg</item>
+        <item name="android:windowEnterAnimation">@android:anim/slide_in_left</item>
+        <item name="android:windowExitAnimation">@android:anim/slide_out_right</item>
+    </style>
+</resources>

+ 21 - 0
app/src/main/res/values/themes.xml

@@ -0,0 +1,21 @@
+<resources xmlns:tools="http://schemas.android.com/tools">
+    <!-- Base application theme. -->
+    <style name="Theme.PluginLoader" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
+        <!-- Primary brand color. -->
+        <item name="colorPrimary">@color/purple_500</item>
+        <item name="colorPrimaryVariant">@color/purple_700</item>
+        <item name="colorOnPrimary">@color/white</item>
+        <!-- Secondary brand color. -->
+        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondaryVariant">@color/teal_700</item>
+        <item name="colorOnSecondary">@color/black</item>
+        <!-- Status bar color. -->
+        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
+        <!-- Customize your theme here. -->
+    </style>
+    <!-- Base application theme. -->
+    <style name="Base.Theme.PluginLoader" parent="Theme.Material3.DayNight.NoActionBar">
+        <!-- Customize your light theme here. -->
+        <!-- <item name="colorPrimary">@color/my_light_primary</item> -->
+    </style>
+</resources>

+ 17 - 0
app/src/test/java/com/example/plugina/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.example.plugina;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 4 - 0
build.gradle

@@ -0,0 +1,4 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+alias(libs.plugins.android.application) apply false
+}

+ 1 - 0
cdv-gradle-config.json

@@ -0,0 +1 @@
+{"MIN_SDK_VERSION":24,"SDK_VERSION":34,"COMPILE_SDK_VERSION":null,"GRADLE_VERSION":"8.7","MIN_BUILD_TOOLS_VERSION":"34.0.0","AGP_VERSION":"8.3.0","KOTLIN_VERSION":"1.9.24","ANDROIDX_APP_COMPAT_VERSION":"1.6.1","ANDROIDX_WEBKIT_VERSION":"1.6.0","ANDROIDX_CORE_SPLASHSCREEN_VERSION":"1.0.0","GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION":"4.3.15","IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED":false,"IS_GRADLE_PLUGIN_KOTLIN_ENABLED":false,"PACKAGE_NAMESPACE":"xpl.android.hello","JAVA_SOURCE_COMPATIBILITY":8,"JAVA_TARGET_COMPATIBILITY":8,"KOTLIN_JVM_TARGET":null}

+ 26 - 0
dist/CJINFO.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Root>
+  <Apk>
+    <Type>pkg</Type>
+    <FileName>app-debug.apk</FileName>
+    <PackageName>com.example.plugina</PackageName>
+    <MainClass>com.example.plugina.Man</MainClass>
+  </Apk>
+  <Version>24.6.18.1</Version>
+  <MODULENAME>测试插件</MODULENAME>
+  <ITEM>
+    <Name>JAR和SO</Name>
+    <Img>yhjk.svg</Img>
+    <Src>index.html#/lowcode/user.list</Src>
+    <FuncMode>2</FuncMode>
+    <Id>video</Id>
+    <Width>40%</Width>
+    <Height>100%</Height>
+    <Right>0%</Right>
+    <Top>0</Top>
+	<BuildType>ZHZDXW|ZHZDDW|MJZD</BuildType>
+  </ITEM>
+  <KFDW>
+  </KFDW>
+  <BuildType>ZHZDXW|ZHZDDW|MJZD</BuildType>
+</Root>

BIN
dist/apk/app-debug.apk


+ 71 - 0
dist/index.html

@@ -0,0 +1,71 @@
+<html>
+    <header>
+            <script src="apkloader.js"></script>
+    </header>
+    <body style="background: #292929;">
+    <div id="divTest" style="color: #fff;">557766</div>
+    <button onclick="sayNativeHello()">回显C++和111</button>
+    <div id="bb" style="height: 50px;"></div>
+    <button onclick="startPicActivity()">选择图片</button>
+    <div id="aa" style="height: 50px;"></div>
+	<input id="txtA" value="5"></input> <input id="txtB" value="6"></input>
+    <div style="height: 4px;"></div>
+    <button id="btnSum" onclick="bindService()">绑定Service并计算上边两个数之和</button>
+    <div id="cc" style="height: 50px;"></div>
+    <button onclick="unbindService()">解绑Service</button>
+    <div id="dd" style="height: 50px;"></div>
+    <button onclick="startActivity()">启动Activity</button>
+    <div id="ee" style="height: 50px;"></div>
+    <button onclick="back()">返回</button>
+    </body>
+    <script>
+            ApkLoader.setPackageName("com.example.plugina");
+            function sayNativeHello(){
+                ApkLoader.call("sayHello", {"name": "1111"});
+            }
+
+            function bindService(){
+				let a = document.getElementById("txtA").value;
+				let b = document.getElementById("txtB").value;
+				alert(a + b);
+                ApkLoader.call("bindService", {"a": a, "b": b});
+            }
+
+            function unbindService(){
+                ApkLoader.call("unbindService", {"name": "1111"});
+            }
+
+            function startActivity(){
+                ApkLoader.call("startActivity", {"name": "1111"});
+            }
+
+            function com_example_navitetest_sayNativeHelloEcho(msg){
+                document.getElementById("divTest").innerHTML = msg.msg;
+            }
+
+            function startPicActivity(){
+                ApkLoader.call("startPicActivity", {});
+            }
+
+            function back(){
+                window.parent.frameUtil.hideIframe('CJJARSO')
+            }
+
+            ApkLoader.setEcho("sayNativeHelloEcho", com_example_navitetest_sayNativeHelloEcho);
+			
+			
+			window.addEventListener('message', (event) => {
+				const data = event.data
+				if (!Array.isArray(data)) return
+				const [code, argsJson] = data
+				if (code === 'apkLoaderEcho' && ApkLoader && ApkLoader.echo){
+				  const args = JSON.parse(argsJson);
+				  ApkLoader.echo(...args);
+				  return;
+				}
+				if (code !== 'lowcode') return
+				const args = JSON.parse(argsJson);
+				cordova.callbackFromNative(...args);
+			})
+    </script>
+</html>

+ 8 - 0
dist/yhjk.svg

@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 50 50">
+  <g id="用户监控" transform="translate(-106 -1896)">
+    <rect id="矩形_4233" data-name="矩形 4233" width="50" height="50" transform="translate(106 1896)" fill="none"/>
+    <g id="组_8256" data-name="组 8256" transform="translate(-581.883 5.345)">
+      <path id="监控" d="M171.589,42.753a2.852,2.852,0,0,1,.16.282l2.567,5.222a4.67,4.67,0,0,1-2.037,6.215,4.488,4.488,0,0,1-2.036.489h-22.17a4.594,4.594,0,0,1-4.554-4.634A4.7,4.7,0,0,1,144,48.255l2.566-5.222a2.571,2.571,0,0,1,.16-.281,16.9,16.9,0,0,0,11.113,4.162h2.636A16.9,16.9,0,0,0,171.589,42.753ZM159.157,11A16.092,16.092,0,1,1,143.34,27.089,15.958,15.958,0,0,1,159.157,11Zm0,25.474a6.7,6.7,0,0,0,.005-13.407h-.005a6.7,6.7,0,0,0,0,13.407Zm.14-4.022a2.682,2.682,0,1,1,2.636-2.682A2.658,2.658,0,0,1,159.3,32.452Z" transform="translate(553.726 1882.675)" fill="none" stroke="#fff" stroke-width="2"/>
+    </g>
+  </g>
+</svg>

+ 21 - 0
gradle.properties

@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true

+ 30 - 0
gradle/libs.versions.toml

@@ -0,0 +1,30 @@
+[versions]
+agp = "8.9.0"
+gradle = "8.3.0"
+junit = "4.13.2"
+junitVersion = "1.2.1"
+espressoCore = "3.6.1"
+appcompat = "1.7.0"
+material = "1.12.0"
+constraintlayout = "2.2.1"
+nanohttpd = "2.3.1"
+navigationFragment = "2.6.0"
+navigationUi = "2.6.0"
+okhttp = "4.9.3"
+
+[libraries]
+gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+nanohttpd = { module = "org.nanohttpd:nanohttpd", version.ref = "nanohttpd" }
+navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" }
+navigation-ui = { group = "androidx.navigation", name = "navigation-ui", version.ref = "navigationUi" }
+okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+

BIN
gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Mon Mar 17 16:48:15 CST 2025
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 185 - 0
gradlew

@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"

+ 89 - 0
gradlew.bat

@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 22 - 0
repositories.gradle

@@ -0,0 +1,22 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you under the Apache License, Version 2.0 (the
+   "License"); you may not use this file except in compliance
+   with the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing,
+   software distributed under the License is distributed on an
+   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+   KIND, either express or implied.  See the License for the
+   specific language governing permissions and limitations
+   under the License.
+*/
+
+ext.repos = {
+    google()
+    mavenCentral()
+}

+ 25 - 0
settings.gradle

@@ -0,0 +1,25 @@
+pluginManagement {
+    repositories {
+        google {
+            content {
+                includeGroupByRegex("com\\.android.*")
+                includeGroupByRegex("com\\.google.*")
+                includeGroupByRegex("androidx.*")
+            }
+        }
+        mavenCentral()
+        gradlePluginPortal()
+        maven { url "https://jitpack.io" }
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+        maven { url "https://jitpack.io" }
+    }
+}
+
+rootProject.name = "native_demo"
+include ':app'