@volcengine/react-native-live-pull 1.0.3-rc.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/android/src/main/AndroidManifest.xml +7 -1
  2. package/android/src/main/AndroidManifestNew.xml +15 -1
  3. package/android/src/main/java/com/volcengine/velive/rn/pull/VolcLiveModule.java +28 -20
  4. package/android/src/main/java/com/volcengine/velive/rn/pull/VolcView.java +7 -8
  5. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowHelper.java +225 -0
  6. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/FloatingWindowService.java +253 -0
  7. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/IFloatingWindowHelper.java +80 -0
  8. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/PictureInPictureManager.java +257 -0
  9. package/android/src/main/java/com/volcengine/velive/rn/pull/pictureInpicture/VeLiveRefManager.java +119 -0
  10. package/android/src/main/res/drawable/button_close.xml +9 -0
  11. package/android/src/main/res/drawable/new_window.xml +16 -0
  12. package/android/src/main/res/layout/floating_window_layout.xml +29 -0
  13. package/ios/VeLivePlayerMultiObserver.h +54 -0
  14. package/ios/VeLivePlayerMultiObserver.m +324 -0
  15. package/ios/pictureInpicture/PictureInPictureManager.h +29 -0
  16. package/ios/pictureInpicture/PictureInPictureManager.m +274 -0
  17. package/ios/pictureInpicture/VeLivePictureInPictureController.h +207 -0
  18. package/ios/pictureInpicture/VeLivePictureInPictureController.m +3393 -0
  19. package/lib/commonjs/index.js +1981 -5044
  20. package/lib/module/index.js +1981 -5044
  21. package/lib/typescript/codegen/pack/errorcode.d.ts +27 -27
  22. package/lib/typescript/codegen/pack/types.d.ts +1 -1
  23. package/lib/typescript/core/api.d.ts +88 -1
  24. package/lib/typescript/core/callback.d.ts +52 -0
  25. package/lib/typescript/platforms/android/extends.d.ts +1 -1
  26. package/lib/typescript/platforms/android/pictureInpicture.d.ts +26 -0
  27. package/lib/typescript/platforms/ios/pictureInpicture.d.ts +32 -0
  28. package/package.json +1 -1
@@ -6,4 +6,10 @@
6
6
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
7
7
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
8
8
 
9
- </manifest>
9
+ <application>
10
+ <activity
11
+ android:name="com.volcengine.velive.rn.pull.pictureInpicture.AssistantActivity"
12
+ android:exported="true"
13
+ android:theme="@style/AppTheme.NoActionBar" />
14
+ </application>
15
+ </manifest>
@@ -4,5 +4,19 @@
4
4
  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
5
5
  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
6
6
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
7
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
7
8
 
8
- </manifest>
9
+ <application>
10
+ <activity
11
+ android:name="com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowHelper$AssistantActivity"
12
+ android:exported="true"
13
+ />
14
+
15
+ <service
16
+ android:name="com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowService"
17
+ android:enabled="true"
18
+ android:exported="false"
19
+ android:foregroundServiceType="mediaProjection" />
20
+ </application>
21
+
22
+ </manifest>
@@ -1,7 +1,6 @@
1
1
  package com.volcengine.velive.rn.pull;
2
2
 
3
3
  import androidx.annotation.Nullable;
4
-
5
4
  import com.facebook.react.bridge.Arguments;
6
5
  import com.facebook.react.bridge.Callback;
7
6
  import com.facebook.react.bridge.ReactApplicationContext;
@@ -11,21 +10,21 @@ import com.facebook.react.bridge.ReadableMap;
11
10
  import com.facebook.react.bridge.WritableMap;
12
11
  import com.facebook.react.module.annotations.ReactModule;
13
12
  import com.facebook.react.modules.core.DeviceEventManagerModule;
14
-
15
13
  import com.volcengine.VolcApiEngine.*;
16
14
  import com.volcengine.velive.rn.pull.autogen.MethodSignature;
17
15
 
18
16
  @ReactModule(name = VolcLiveModule.NAME)
19
- public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver {
17
+ public class VolcLiveModule
18
+ extends VolcLiveModuleSpec implements IEventReceiver {
20
19
  public static final String NAME = "VolcLiveModule";
21
-
20
+
22
21
  VolcApiEngine apiEngine = null;
23
-
22
+
24
23
  VolcLiveModule(ReactApplicationContext context) {
25
24
  super(context);
26
25
  MethodSignature.init();
27
26
  }
28
-
27
+
29
28
  @Override
30
29
  public String getName() {
31
30
  return NAME;
@@ -38,12 +37,12 @@ public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver
38
37
  NativeVariableManager.init(apiEngine.msgClient, super.context);
39
38
  return true;
40
39
  }
41
-
40
+
42
41
  return false;
43
42
  }
44
43
 
45
44
  @ReactMethod(isBlockingSynchronousMethod = true)
46
- public String callApiSync(ReadableMap arg) {
45
+ public String callApiSync(ReadableMap arg) {
47
46
  try {
48
47
  newApiEngine();
49
48
  String params = arg.getString("params");
@@ -55,16 +54,26 @@ public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver
55
54
  }
56
55
 
57
56
  @ReactMethod(isBlockingSynchronousMethod = false)
58
- public void callApi(ReadableMap arg, Callback callback) {
57
+ public void callApi(ReadableMap arg, Callback callback) {
59
58
  try {
60
59
  newApiEngine();
61
60
  String params = arg.getString("params");
62
- this.apiEngine.callApi(params, (res) -> {
63
- callback.invoke(res.toJsonString());
64
- });
61
+
62
+ // 获取主线程处理器
63
+ android.os.Handler mainHandler =
64
+ new android.os.Handler(android.os.Looper.getMainLooper());
65
+
66
+ // 在主线程上执行 API 调用
67
+ mainHandler.post(() -> {
68
+ try {
69
+ this.apiEngine.callApi(
70
+ params, (res) -> { callback.invoke(res.toJsonString()); });
71
+ } catch (Exception e) {
72
+ e.printStackTrace();
73
+ }
74
+ });
65
75
  } catch (Exception e) {
66
76
  e.printStackTrace();
67
- throw new RuntimeException(e);
68
77
  }
69
78
  }
70
79
 
@@ -87,12 +96,11 @@ public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver
87
96
  }
88
97
  }
89
98
 
90
- private void sendEvent(ReactContext reactContext,
91
- String eventName,
92
- @Nullable WritableMap params) {
99
+ private void sendEvent(ReactContext reactContext, String eventName,
100
+ @Nullable WritableMap params) {
93
101
  reactContext
94
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
95
- .emit(eventName, params);
102
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
103
+ .emit(eventName, params);
96
104
  }
97
105
 
98
106
  @Override
@@ -102,7 +110,7 @@ public class VolcLiveModule extends VolcLiveModuleSpec implements IEventReceiver
102
110
  map.putString("data", data);
103
111
 
104
112
  super.context
105
- .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
106
- .emit(VolcLiveModuleSpec.EVENT, map);
113
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
114
+ .emit(VolcLiveModuleSpec.EVENT, map);
107
115
  }
108
116
  }
@@ -2,7 +2,6 @@ package com.volcengine.velive.rn.pull;
2
2
 
3
3
  import android.content.Context;
4
4
  import android.widget.FrameLayout;
5
-
6
5
  import com.facebook.react.bridge.Arguments;
7
6
  import com.facebook.react.bridge.ReactContext;
8
7
  import com.facebook.react.bridge.WritableMap;
@@ -11,13 +10,14 @@ import com.facebook.react.uimanager.events.RCTEventEmitter;
11
10
  public class VolcView extends FrameLayout {
12
11
  public String viewId;
13
12
  public boolean hasRegister = false;
14
-
15
- public VolcView(Context context) {
13
+
14
+ public VolcView(Context context) {
16
15
  super(context);
16
+ this.setKeepScreenOn(true);
17
17
  }
18
-
18
+
19
19
  public void setViewId(String viewId) {
20
- this.viewId = viewId;
20
+ this.viewId = viewId;
21
21
  this.hasRegister = true;
22
22
  this.emitOnLoad();
23
23
  }
@@ -25,8 +25,7 @@ public class VolcView extends FrameLayout {
25
25
  public void emitOnLoad() {
26
26
  WritableMap event = Arguments.createMap();
27
27
  ReactContext reactContext = (ReactContext)getContext();
28
- reactContext
29
- .getJSModule(RCTEventEmitter.class)
30
- .receiveEvent(getId(), "load", event);
28
+ reactContext.getJSModule(RCTEventEmitter.class)
29
+ .receiveEvent(getId(), "load", event);
31
30
  }
32
31
  }
@@ -0,0 +1,225 @@
1
+ package com.volcengine.velive.rn.pull.pictureInpicture;
2
+
3
+ import static com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowService.INTENT_EXTRA_KEY_ASPECT_RATIO;
4
+ import static com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowService.INTENT_EXTRA_KEY_X_POS;
5
+ import static com.volcengine.velive.rn.pull.pictureInpicture.FloatingWindowService.INTENT_EXTRA_KEY_Y_POS;
6
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_INVALID_PARAMS;
7
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_IS_ALREADY_OPEN;
8
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_NO;
9
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_NOT_SUPPORT;
10
+ import static com.volcengine.velive.rn.pull.pictureInpicture.IFloatingWindowHelper.Listener.ERR_RETRY;
11
+
12
+ import android.app.Activity;
13
+ import android.content.Context;
14
+ import android.content.Intent;
15
+ import android.net.Uri;
16
+ import android.os.Build;
17
+ import android.os.Bundle;
18
+ import android.provider.Settings;
19
+ import android.view.SurfaceView;
20
+ import androidx.annotation.Nullable;
21
+ import java.util.HashMap;
22
+ import java.util.Map;
23
+
24
+ public class FloatingWindowHelper implements IFloatingWindowHelper {
25
+ private static FloatingWindowHelper mInstance = null;
26
+ private Listener mListener;
27
+ private Config mConfig;
28
+ private Map<String, Object> mExtraDataMap;
29
+ private boolean isRunning;
30
+
31
+ public synchronized static FloatingWindowHelper getInstance() {
32
+ if (mInstance == null) {
33
+ mInstance = new FloatingWindowHelper();
34
+ }
35
+ return mInstance;
36
+ }
37
+
38
+ public static boolean isPictureInPictureSupported() {
39
+ // Check if the system version supports floating windows
40
+ if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) {
41
+ // Android versions below 6.0 don't support SYSTEM_ALERT_WINDOW permission
42
+ // management
43
+ return false;
44
+ }
45
+ // The system supports PIP mode
46
+ return true;
47
+ }
48
+
49
+ @Override
50
+ public void setEventListener(Listener listener) {
51
+ mListener = listener;
52
+ }
53
+
54
+ public Listener getEventListener() { return mListener; }
55
+
56
+ public void openFloatingWindow(Context context, Config config,
57
+ Map<String, Object> extraData) {
58
+ if (isRunning) {
59
+ if (mListener != null) {
60
+ mListener.onOpenFloatingWindowResult(ERR_IS_ALREADY_OPEN, extraData);
61
+ }
62
+ return;
63
+ }
64
+ // Invalid parameters
65
+ if (context == null || config.aspectRatio <= 0) {
66
+ if (mListener != null) {
67
+ mListener.onOpenFloatingWindowResult(ERR_INVALID_PARAMS, extraData);
68
+ }
69
+ return;
70
+ }
71
+
72
+ // Android versions below 6.0 don't support Overlay functionality
73
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
74
+ if (mListener != null) {
75
+ mListener.onOpenFloatingWindowResult(ERR_NOT_SUPPORT, extraData);
76
+ }
77
+ return;
78
+ }
79
+
80
+ // Save configuration first, so it can be used in permission request
81
+ // callback
82
+ mConfig = config;
83
+ mExtraDataMap = extraData;
84
+
85
+ // If Overlay permission is not enabled, request it first
86
+ if (!Settings.canDrawOverlays(context)) {
87
+ if (mListener != null && mListener.onRequestOverlayPermission()) {
88
+ requestOverlayPermission(context);
89
+ } else {
90
+ // User doesn't allow permission request, trigger error callback
91
+ if (mListener != null) {
92
+ mListener.onOpenFloatingWindowResult(ERR_INVALID_PARAMS, extraData);
93
+ }
94
+ }
95
+ return;
96
+ }
97
+
98
+ Intent intent = new Intent(context, FloatingWindowService.class);
99
+ intent.putExtra(INTENT_EXTRA_KEY_ASPECT_RATIO, mConfig.aspectRatio);
100
+ intent.putExtra(INTENT_EXTRA_KEY_X_POS, mConfig.x);
101
+ intent.putExtra(INTENT_EXTRA_KEY_Y_POS, mConfig.y);
102
+ context.startService(intent);
103
+ isRunning = true;
104
+ }
105
+
106
+ @Override
107
+ public void closeFloatingWindow(Context context) {
108
+ if (!isRunning) {
109
+ return;
110
+ }
111
+ context.stopService(new Intent(context, FloatingWindowService.class));
112
+ isRunning = false;
113
+ }
114
+
115
+ @Override
116
+ public void requestOverlayPermission(Context context) {
117
+ try {
118
+ Intent intent = new Intent(context, AssistantActivity.class);
119
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
120
+ context.startActivity(intent);
121
+ } catch (Exception e) {
122
+ // If starting AssistantActivity fails, trigger error callback
123
+ if (mListener != null) {
124
+ Map<String, Object> errorData = new HashMap<>();
125
+ errorData.put("error", e.getMessage());
126
+ mListener.onOpenFloatingWindowResult(ERR_NOT_SUPPORT, errorData);
127
+ }
128
+ }
129
+ }
130
+
131
+ @Override
132
+ public Config getConfig() {
133
+ return mConfig;
134
+ }
135
+
136
+ @Override
137
+ public Map<String, Object> getExtraData() {
138
+ return mExtraDataMap;
139
+ }
140
+
141
+ @Override
142
+ public boolean isOpen() {
143
+ return isRunning;
144
+ }
145
+
146
+ void setSurfaceView(SurfaceView surfaceView) {
147
+ if (mListener != null) {
148
+ mListener.onUpdateSurfaceView(surfaceView);
149
+ }
150
+ }
151
+
152
+ void performClose(Context context) {
153
+ if (mListener != null) {
154
+ mListener.onClickFloatingWindowCloseBtn(context);
155
+ }
156
+ }
157
+
158
+ void onClickFloatingWindow(Context context) {
159
+ if (mListener != null) {
160
+ mListener.onClickFloatingWindow(context);
161
+ }
162
+ }
163
+
164
+ void onStartService() {
165
+ if (mListener != null) {
166
+ mListener.onOpenFloatingWindowResult(ERR_NO, mExtraDataMap);
167
+ }
168
+ }
169
+
170
+ void onStopService() {
171
+ if (mListener != null) {
172
+ mListener.onCloseFloatingWindow(mExtraDataMap);
173
+ }
174
+
175
+ mConfig = null;
176
+ mExtraDataMap = null;
177
+ isRunning = false;
178
+ }
179
+
180
+ public static class AssistantActivity extends Activity {
181
+ private Listener mListener;
182
+
183
+ @Override
184
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
185
+ super.onCreate(savedInstanceState);
186
+ mListener = FloatingWindowHelper.getInstance().getEventListener();
187
+
188
+ int sdkInt = Build.VERSION.SDK_INT;
189
+ int mOverlayRequestCode = 1001;
190
+ if (sdkInt >= Build.VERSION_CODES.O) { // Android 8.0 and above
191
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
192
+ startActivityForResult(intent, mOverlayRequestCode);
193
+ } else if (sdkInt >= Build.VERSION_CODES.M) {
194
+ Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
195
+ intent.setData(Uri.parse("package:" + getPackageName()));
196
+ startActivityForResult(intent, mOverlayRequestCode);
197
+ }
198
+ }
199
+
200
+ @Override
201
+ protected void onActivityResult(int requestCode, int resultCode,
202
+ @Nullable Intent data) {
203
+ super.onActivityResult(requestCode, resultCode, data);
204
+
205
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
206
+ if (!Settings.canDrawOverlays(this)) {
207
+ // User denied permission request, return permission error
208
+ if (mListener != null) {
209
+ mListener.onOpenFloatingWindowResult(ERR_INVALID_PARAMS, null);
210
+ }
211
+ } else {
212
+ // User granted permission
213
+ if (mListener != null) {
214
+ // Return success directly, let the caller retry
215
+ // startPictureInPicture
216
+ mListener.onOpenFloatingWindowResult(ERR_RETRY, null);
217
+ }
218
+ }
219
+ }
220
+
221
+ // Finish activity regardless of the result
222
+ finish();
223
+ }
224
+ }
225
+ }
@@ -0,0 +1,253 @@
1
+ package com.volcengine.velive.rn.pull.pictureInpicture;
2
+
3
+ import android.app.Service;
4
+ import android.content.BroadcastReceiver;
5
+ import android.content.Context;
6
+ import android.content.Intent;
7
+ import android.content.IntentFilter;
8
+ import android.graphics.PixelFormat;
9
+ import android.os.Build;
10
+ import android.os.Handler;
11
+ import android.os.IBinder;
12
+ import android.os.Looper;
13
+ import android.provider.Settings;
14
+ import android.util.DisplayMetrics; // Import DisplayMetrics
15
+ import android.util.Log;
16
+ import android.view.Gravity;
17
+ import android.view.LayoutInflater;
18
+ import android.view.MotionEvent;
19
+ import android.view.SurfaceView;
20
+ import android.view.View;
21
+ import android.view.ViewConfiguration;
22
+ import android.view.WindowManager;
23
+ import androidx.annotation.Nullable;
24
+ import com.volcengine.velive.rn.pull.R;
25
+
26
+ public class FloatingWindowService extends Service {
27
+ private static final String TAG = FloatingWindowService.class.getSimpleName();
28
+ private static final int LONGER_SIDE_MAX_LEN = 1000;
29
+
30
+ private WindowManager mWindowManager;
31
+ private WindowManager.LayoutParams mLayoutParams;
32
+ private SurfaceView mSurfaceView;
33
+ private View mSmallWindowView;
34
+ private ActivityLaunchReceiver mActivityLaunchReceiver;
35
+
36
+ public static final String ACTION_STOP_PIP_SERVICE =
37
+ "com.volcengine.velive.rn.pull.STOP_PIP_SERVICE";
38
+ public static final String INTENT_EXTRA_KEY_ASPECT_RATIO = "aspect_ratio";
39
+ public static final String INTENT_EXTRA_KEY_X_POS = "x_pos";
40
+ public static final String INTENT_EXTRA_KEY_Y_POS = "y_pos";
41
+
42
+ @Override
43
+ public void onCreate() {
44
+ Log.d(TAG, "onCreate");
45
+ super.onCreate();
46
+ FloatingWindowHelper.getInstance().onStartService();
47
+ }
48
+
49
+ @Override
50
+ public int onStartCommand(Intent intent, int flags, int startId) {
51
+ Log.d(TAG, "onStartCommand");
52
+ initUI(intent.getFloatExtra(INTENT_EXTRA_KEY_ASPECT_RATIO, 16f / 9f),
53
+ intent.getIntExtra(INTENT_EXTRA_KEY_X_POS, 300),
54
+ intent.getIntExtra(INTENT_EXTRA_KEY_Y_POS, 300));
55
+ FloatingWindowHelper.getInstance().setSurfaceView(mSurfaceView);
56
+ return super.onStartCommand(intent, flags, startId);
57
+ }
58
+
59
+ @Override
60
+ public void onDestroy() {
61
+ Log.d(TAG, "onDestroy");
62
+ super.onDestroy();
63
+ unregisterActivityLaunchReceiver(); // Ensure receiver is unregistered on
64
+ // destroy
65
+ if (mSmallWindowView != null) {
66
+ try {
67
+ mWindowManager.removeView(mSmallWindowView);
68
+ } catch (Exception e) {
69
+ Log.e(TAG, "Error removing view: " + e.getMessage());
70
+ }
71
+ }
72
+ FloatingWindowHelper.getInstance().onStopService();
73
+ }
74
+
75
+ @Nullable
76
+ @Override
77
+ public IBinder onBind(Intent intent) {
78
+ return null;
79
+ }
80
+
81
+ private void initUI(float aspectRatio, int x, int y) {
82
+ mWindowManager = (WindowManager)getSystemService(WINDOW_SERVICE);
83
+ mLayoutParams = new WindowManager.LayoutParams();
84
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
85
+ mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
86
+ } else {
87
+ mLayoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
88
+ }
89
+ mLayoutParams.format = PixelFormat.RGBA_8888;
90
+ mLayoutParams.gravity = Gravity.START | Gravity.TOP;
91
+ mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
92
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
93
+
94
+ // Get screen dimensions
95
+ DisplayMetrics displayMetrics = new DisplayMetrics();
96
+ mWindowManager.getDefaultDisplay().getMetrics(displayMetrics);
97
+ int screenWidth = displayMetrics.widthPixels;
98
+ int screenHeight = displayMetrics.heightPixels;
99
+
100
+ // Calculate the minimum screen dimension
101
+ int minScreenDim = Math.min(screenWidth, screenHeight);
102
+
103
+ // Determine the maximum length based on the smaller of 1000 and
104
+ // minScreenDim
105
+ int maxLen = Math.min(LONGER_SIDE_MAX_LEN, minScreenDim);
106
+
107
+ // Limit the floating window size to prevent it from being too large or too
108
+ // small, control the longer side to maxLen, scale the shorter
109
+ // side proportionally
110
+ int width, height;
111
+ if (aspectRatio >= 1) {
112
+ height = (int)(maxLen / aspectRatio);
113
+ width = maxLen;
114
+ } else {
115
+ width = (int)(maxLen * aspectRatio);
116
+ height = maxLen;
117
+ }
118
+ mLayoutParams.width = width;
119
+ mLayoutParams.height = height;
120
+
121
+ // Initial position of the floating window
122
+ mLayoutParams.x = x;
123
+ mLayoutParams.y = y;
124
+
125
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
126
+ if (Settings.canDrawOverlays(this)) {
127
+ LayoutInflater layoutInflater = LayoutInflater.from(this);
128
+ mSmallWindowView =
129
+ layoutInflater.inflate(R.layout.floating_window_layout, null);
130
+ mSmallWindowView.setOnTouchListener(new FloatingOnTouchListener());
131
+ mWindowManager.addView(mSmallWindowView, mLayoutParams);
132
+ mSurfaceView = mSmallWindowView.findViewById(R.id.surface_view);
133
+ mSmallWindowView.findViewById(R.id.surface_close_btn)
134
+ .setOnClickListener(v -> {
135
+ unregisterActivityLaunchReceiver();
136
+ stopSelf();
137
+ });
138
+
139
+ mSmallWindowView.findViewById(R.id.new_window_btn)
140
+ .setOnClickListener(v -> {
141
+ Log.d(TAG, "PIP window clicked");
142
+
143
+ try {
144
+ // Get the pacain activity
145
+ String packageName = this.getPackageName();
146
+ Intent launchIntent =
147
+ this.getPackageManager().getLaunchIntentForPackage(
148
+ packageName);
149
+
150
+ if (launchIntent != null) {
151
+ Log.d(TAG, "Launching app with intent: " + launchIntent);
152
+ launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
153
+ // Register receiver to stop service when activity is launched
154
+ registerActivityLaunchReceiver();
155
+ this.startActivity(launchIntent);
156
+ } else {
157
+ Log.e(TAG, "Could not create launch intent for package: " +
158
+ packageName);
159
+ }
160
+ } catch (Exception e) {
161
+ Log.e(TAG, "Error launching app: " + e.getMessage(), e);
162
+ }
163
+ });
164
+ }
165
+ }
166
+ }
167
+
168
+ private void registerActivityLaunchReceiver() {
169
+ if (mActivityLaunchReceiver == null) {
170
+ mActivityLaunchReceiver = new ActivityLaunchReceiver();
171
+ IntentFilter filter = new IntentFilter(ACTION_STOP_PIP_SERVICE);
172
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
173
+ registerReceiver(mActivityLaunchReceiver, filter,
174
+ Context.RECEIVER_EXPORTED);
175
+ } else {
176
+ registerReceiver(mActivityLaunchReceiver, filter);
177
+ }
178
+ Log.d(TAG, "ActivityLaunchReceiver registered");
179
+ }
180
+ }
181
+
182
+ private void unregisterActivityLaunchReceiver() {
183
+ if (mActivityLaunchReceiver != null) {
184
+ try {
185
+ unregisterReceiver(mActivityLaunchReceiver);
186
+ mActivityLaunchReceiver = null;
187
+ Log.d(TAG, "ActivityLaunchReceiver unregistered");
188
+ } catch (IllegalArgumentException e) {
189
+ Log.e(TAG, "Receiver not registered: " + e.getMessage());
190
+ }
191
+ }
192
+ }
193
+
194
+ // BroadcastReceiver to listen for activity launch confirmation
195
+ private class ActivityLaunchReceiver extends BroadcastReceiver {
196
+ @Override
197
+ public void onReceive(Context context, Intent intent) {
198
+ Log.d(TAG, "Received broadcast to stop PIP service");
199
+ if (ACTION_STOP_PIP_SERVICE.equals(intent.getAction())) {
200
+ stopSelf(); // Stop the service
201
+ unregisterActivityLaunchReceiver(); // Unregister receiver
202
+ }
203
+ }
204
+ }
205
+
206
+ private class FloatingOnTouchListener implements View.OnTouchListener {
207
+ private final int touchSlop =
208
+ ViewConfiguration
209
+ .get(FloatingWindowService.this.getApplicationContext())
210
+ .getScaledTouchSlop();
211
+ private int downX, downY;
212
+ private int x, y;
213
+ private boolean isDragging;
214
+
215
+ @Override
216
+ public boolean onTouch(View view, MotionEvent event) {
217
+ switch (event.getAction()) {
218
+ case MotionEvent.ACTION_UP:
219
+ if (isDragging) {
220
+ isDragging = false;
221
+ } else {
222
+ FloatingWindowHelper.getInstance().onClickFloatingWindow(
223
+ FloatingWindowService.this);
224
+ }
225
+ downX = downY = 0;
226
+ break;
227
+ case MotionEvent.ACTION_DOWN:
228
+ downX = x = (int)event.getRawX();
229
+ downY = y = (int)event.getRawY();
230
+ break;
231
+ case MotionEvent.ACTION_MOVE:
232
+ int nowX = (int)event.getRawX();
233
+ int nowY = (int)event.getRawY();
234
+ if (Math.abs(nowX - downX) > touchSlop ||
235
+ Math.abs(nowY - downY) > touchSlop) {
236
+ isDragging = true;
237
+ }
238
+ int movedX = nowX - x;
239
+ int movedY = nowY - y;
240
+ x = nowX;
241
+ y = nowY;
242
+ mLayoutParams.x = mLayoutParams.x + movedX;
243
+ mLayoutParams.y = mLayoutParams.y + movedY;
244
+ mWindowManager.updateViewLayout(view, mLayoutParams);
245
+ break;
246
+
247
+ default:
248
+ break;
249
+ }
250
+ return true;
251
+ }
252
+ }
253
+ }