19. 核心服务CallBack的IBinder设计

本文详细介绍了如何进行正向调用核心服务以及核心服务的远程回调机制。通过NativeService范例代码,展示了服务的实现过程,同时提供了Client模块的代码示例,帮助开发者理解并应用到实际项目中。重点讨论了服务交互和回调流程,为软件开发提供实用参考。
你现在是一名资深的蓝牙工程师,请你详细讲解下面的BluetoothServiceBinder.java的代码: package com.android.server.bluetooth; 18 19 import static android.Manifest.permission.BLUETOOTH_CONNECT; 20 import static android.Manifest.permission.BLUETOOTH_PRIVILEGED; 21 import static android.Manifest.permission.DUMP; 22 import static android.Manifest.permission.LOCAL_MAC_ADDRESS; 23 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 24 25 import static com.android.server.bluetooth.BtPermissionUtils.checkConnectPermissionForDataDelivery; 26 import static com.android.server.bluetooth.BtPermissionUtils.getCallingAppId; 27 import static com.android.server.bluetooth.BtPermissionUtils.isCallerSystem; 28 29 import static java.util.Objects.requireNonNull; 30 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.annotation.RequiresPermission; 34 import android.app.AppOpsManager; 35 import android.bluetooth.BluetoothAdapter; 36 import android.bluetooth.IBluetooth; 37 import android.bluetooth.IBluetoothManager; 38 import android.bluetooth.IBluetoothManagerCallback; 39 import android.content.AttributionSource; 40 import android.content.Context; 41 import android.os.Build; 42 import android.os.IBinder; 43 import android.os.Looper; 44 import android.os.ParcelFileDescriptor; 45 import android.os.UserManager; 46 import android.permission.PermissionManager; 47 48 import androidx.annotation.RequiresApi; 49 50 import java.io.FileDescriptor; 51 import java.io.PrintWriter; 52 53 class BluetoothServiceBinder extends IBluetoothManager.Stub { 54 private static final String TAG = BluetoothServiceBinder.class.getSimpleName(); 55 56 private final BluetoothManagerService mBluetoothManagerService; 57 private final Context mContext; 58 private final UserManager mUserManager; 59 private final AppOpsManager mAppOpsManager; 60 private final PermissionManager mPermissionManager; 61 private final BtPermissionUtils mPermissionUtils; 62 private final Looper unusedmLooper; 63 64 BluetoothServiceBinder( 65 BluetoothManagerService bms, Looper looper, Context ctx, UserManager userManager) { 66 mBluetoothManagerService = bms; 67 unusedmLooper = looper; 68 mContext = ctx; 69 mUserManager = userManager; 70 mAppOpsManager = 71 requireNonNull( 72 ctx.getSystemService(AppOpsManager.class), 73 "AppOpsManager system service cannot be null"); 74 mPermissionManager = 75 requireNonNull( 76 ctx.getSystemService(PermissionManager.class), 77 "PermissionManager system service cannot be null"); 78 mPermissionUtils = new BtPermissionUtils(ctx); 79 } 80 81 @Override 82 @Nullable 83 public IBinder registerAdapter(@NonNull IBluetoothManagerCallback callback) { 84 requireNonNull(callback, "Callback cannot be null in registerAdapter"); 85 IBluetooth bluetooth = mBluetoothManagerService.registerAdapter(callback); 86 if (bluetooth == null) { 87 return null; 88 } 89 return bluetooth.asBinder(); 90 } 91 92 @Override 93 public void unregisterAdapter(@NonNull IBluetoothManagerCallback callback) { 94 requireNonNull(callback, "Callback cannot be null in unregisterAdapter"); 95 mBluetoothManagerService.unregisterAdapter(callback); 96 } 97 98 @Override 99 public boolean enable(@NonNull AttributionSource source) { 100 requireNonNull(source, "AttributionSource cannot be null in enable"); 101 102 final String errorMsg = 103 mPermissionUtils.callerCanToggle( 104 mContext, 105 source, 106 mUserManager, 107 mAppOpsManager, 108 mPermissionManager, 109 "enable", 110 true); 111 if (!errorMsg.isEmpty()) { 112 Log.d(TAG, "enable(): FAILED: " + errorMsg); 113 return false; 114 } 115 116 return mBluetoothManagerService.enable(source.getPackageName()); 117 } 118 119 @Override 120 public boolean enableNoAutoConnect(AttributionSource source) { 121 requireNonNull(source, "AttributionSource cannot be null in enableNoAutoConnect"); 122 123 final String errorMsg = 124 mPermissionUtils.callerCanToggle( 125 mContext, 126 source, 127 mUserManager, 128 mAppOpsManager, 129 mPermissionManager, 130 "enableNoAutoConnect", 131 false); 132 if (!errorMsg.isEmpty()) { 133 Log.d(TAG, "enableNoAutoConnect(): FAILED: " + errorMsg); 134 return false; 135 } 136 137 if (!BtPermissionUtils.isCallerNfc(getCallingAppId())) { 138 throw new SecurityException("No permission to enable Bluetooth quietly"); 139 } 140 141 return mBluetoothManagerService.enableNoAutoConnect(source.getPackageName()); 142 } 143 144 @Override 145 public boolean disable(AttributionSource source, boolean persist) { 146 requireNonNull(source, "AttributionSource cannot be null in disable"); 147 148 if (!persist) { 149 BtPermissionUtils.enforcePrivileged(mContext); 150 } 151 152 final String errorMsg = 153 mPermissionUtils.callerCanToggle( 154 mContext, 155 source, 156 mUserManager, 157 mAppOpsManager, 158 mPermissionManager, 159 "disable", 160 true); 161 if (!errorMsg.isEmpty()) { 162 Log.d(TAG, "disable(): FAILED: " + errorMsg); 163 return false; 164 } 165 166 return mBluetoothManagerService.disable(source.getPackageName(), persist); 167 } 168 169 @Override 170 public int getState() { 171 if (!isCallerSystem(getCallingAppId()) 172 && !mPermissionUtils.checkIfCallerIsForegroundUser(mUserManager)) { 173 Log.w(TAG, "getState(): UNAUTHORIZED. Report OFF for non-active and non system user"); 174 return BluetoothAdapter.STATE_OFF; 175 } 176 177 return mBluetoothManagerService.getState(); 178 } 179 180 @Override 181 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, LOCAL_MAC_ADDRESS}) 182 public String getAddress(AttributionSource source) { 183 requireNonNull(source, "AttributionSource cannot be null in getAddress"); 184 185 if (!checkConnectPermissionForDataDelivery( 186 mContext, mPermissionManager, source, "getAddress")) { 187 return null; 188 } 189 190 if (!isCallerSystem(getCallingAppId()) 191 && !mPermissionUtils.checkIfCallerIsForegroundUser(mUserManager)) { 192 Log.w(TAG, "getAddress(): Not allowed for non-active and non system user"); 193 return null; 194 } 195 196 if (mContext.checkCallingOrSelfPermission(LOCAL_MAC_ADDRESS) != PERMISSION_GRANTED) { 197 // TODO(b/280890575): Throws a SecurityException instead 198 Log.w(TAG, "getAddress(): Client does not have LOCAL_MAC_ADDRESS permission"); 199 return BluetoothAdapter.DEFAULT_MAC_ADDRESS; 200 } 201 202 return mBluetoothManagerService.getAddress(); 203 } 204 205 @Override 206 @RequiresPermission(BLUETOOTH_CONNECT) 207 public String getName(AttributionSource source) { 208 requireNonNull(source, "AttributionSource cannot be null in getName"); 209 210 if (!checkConnectPermissionForDataDelivery( 211 mContext, mPermissionManager, source, "getName")) { 212 return null; 213 } 214 215 if (!isCallerSystem(getCallingAppId()) 216 && !mPermissionUtils.checkIfCallerIsForegroundUser(mUserManager)) { 217 Log.w(TAG, "getName(): not allowed for non-active and non system user"); 218 return null; 219 } 220 221 return mBluetoothManagerService.getName(); 222 } 223 224 @Override 225 @RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED}) 226 public boolean onFactoryReset(AttributionSource source) { 227 requireNonNull(source, "AttributionSource cannot be null in onFactoryReset"); 228 229 BtPermissionUtils.enforcePrivileged(mContext); 230 231 if (!checkConnectPermissionForDataDelivery( 232 mContext, mPermissionManager, source, "onFactoryReset")) { 233 return false; 234 } 235 236 return mBluetoothManagerService.onFactoryReset(); 237 } 238 239 @Override 240 public boolean factoryReset() { 241 return true; 242 } 243 244 public boolean isBleScanAvailable() { 245 return mBluetoothManagerService.isBleScanAvailable(); 246 } 247 248 @Override 249 @RequiresPermission(BLUETOOTH_CONNECT) 250 public boolean enableBle(AttributionSource source, IBinder token) { 251 requireNonNull(source, "AttributionSource cannot be null in enableBle"); 252 requireNonNull(token, "IBinder cannot be null in enableBle"); 253 254 final String errorMsg = 255 mPermissionUtils.callerCanToggle( 256 mContext, 257 source, 258 mUserManager, 259 mAppOpsManager, 260 mPermissionManager, 261 "enableBle", 262 false); 263 if (!errorMsg.isEmpty()) { 264 Log.d(TAG, "enableBle(): FAILED: " + errorMsg); 265 return false; 266 } 267 268 return mBluetoothManagerService.enableBle(source.getPackageName(), token); 269 } 270 271 @Override 272 @RequiresPermission(BLUETOOTH_CONNECT) 273 public boolean disableBle(AttributionSource source, IBinder token) { 274 requireNonNull(source, "AttributionSource cannot be null in disableBle"); 275 requireNonNull(token, "IBinder cannot be null in disableBle"); 276 277 final String errorMsg = 278 mPermissionUtils.callerCanToggle( 279 mContext, 280 source, 281 mUserManager, 282 mAppOpsManager, 283 mPermissionManager, 284 "disableBle", 285 false); 286 if (!errorMsg.isEmpty()) { 287 Log.d(TAG, "disableBle(): FAILED: " + errorMsg); 288 return false; 289 } 290 291 return mBluetoothManagerService.disableBle(source.getPackageName(), token); 292 } 293 294 @Override 295 public boolean isHearingAidProfileSupported() { 296 return mBluetoothManagerService.isHearingAidProfileSupported(); 297 } 298 299 @Override 300 @RequiresPermission(BLUETOOTH_PRIVILEGED) 301 public int setBtHciSnoopLogMode(int mode) { 302 BtPermissionUtils.enforcePrivileged(mContext); 303 304 return mBluetoothManagerService.setBtHciSnoopLogMode(mode); 305 } 306 307 @Override 308 @RequiresPermission(BLUETOOTH_PRIVILEGED) 309 public int getBtHciSnoopLogMode() { 310 BtPermissionUtils.enforcePrivileged(mContext); 311 312 return mBluetoothManagerService.getBtHciSnoopLogMode(); 313 } 314 315 @Override 316 public int handleShellCommand( 317 @NonNull ParcelFileDescriptor in, 318 @NonNull ParcelFileDescriptor out, 319 @NonNull ParcelFileDescriptor err, 320 @NonNull String[] args) { 321 return new BluetoothShellCommand(mBluetoothManagerService) 322 .exec( 323 this, 324 in.getFileDescriptor(), 325 out.getFileDescriptor(), 326 err.getFileDescriptor(), 327 args); 328 } 329 330 @Override 331 @RequiresPermission(BLUETOOTH_PRIVILEGED) 332 public boolean isAutoOnSupported() { 333 BtPermissionUtils.enforcePrivileged(mContext); 334 return mBluetoothManagerService.isAutoOnSupported(); 335 } 336 337 @Override 338 @RequiresPermission(BLUETOOTH_PRIVILEGED) 339 public boolean isAutoOnEnabled() { 340 BtPermissionUtils.enforcePrivileged(mContext); 341 return mBluetoothManagerService.isAutoOnEnabled(); 342 } 343 344 @Override 345 @RequiresPermission(BLUETOOTH_PRIVILEGED) 346 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 347 public void setAutoOnEnabled(boolean status) { 348 BtPermissionUtils.enforcePrivileged(mContext); 349 mBluetoothManagerService.setAutoOnEnabled(status); 350 } 351 352 @Override 353 @RequiresPermission(DUMP) 354 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 355 if (mContext.checkCallingOrSelfPermission(DUMP) != PERMISSION_GRANTED) { 356 // TODO(b/280890575): Throws SecurityException instead 357 Log.w(TAG, "dump(): Client does not have DUMP permission"); 358 return; 359 } 360 361 mBluetoothManagerService.dump(fd, writer, args); 362 } 363 }
07-18
Index: app/src/main/AndroidManifest.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml --- a/app/src/main/AndroidManifest.xml (revision 87afe212d78a0d91635bca6e882a6fa407331bc5) +++ b/app/src/main/AndroidManifest.xml (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -19,6 +19,8 @@ tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" /> + <uses-permission android:name="com.oplus.permission.safe.MAGNIFIER" /> + <permission android:name="com.oplus.permission.safe.MAGNIFIER"/> <application android:name=".RomServerApplication" @@ -98,6 +100,16 @@ <action android:name="android.content.action.AI_NOTIFY_BRIEF_AUTO_EXECUTION"/> </intent-filter> </receiver> + <!--游戏助手放大镜功能--> + <service android:name=".magnifier.MagnifierService" + android:enabled="true" + android:permission="com.oplus.permission.safe.MAGNIFIER" + android:exported="true"> + <intent-filter> + <action android:name="com.oplus.intent.action.MAGNIFIER" /> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </service> </application> </manifest> \ No newline at end of file Index: app/src/main/aidl/com/oplus/rom/server/magnifier/IMagnifierCallback.aidl IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/aidl/com/oplus/rom/server/magnifier/IMagnifierCallback.aidl b/app/src/main/aidl/com/oplus/rom/server/magnifier/IMagnifierCallback.aidl new file mode 100644 --- /dev/null (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) +++ b/app/src/main/aidl/com/oplus/rom/server/magnifier/IMagnifierCallback.aidl (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -0,0 +1,5 @@ +package com.oplus.rom.server.magnifier; + +interface IMagnifierCallback { + +} \ No newline at end of file Index: app/src/main/aidl/com/oplus/rom/server/magnifier/IMagnifierService.aidl IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/aidl/com/oplus/rom/server/magnifier/IMagnifierService.aidl b/app/src/main/aidl/com/oplus/rom/server/magnifier/IMagnifierService.aidl new file mode 100644 --- /dev/null (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) +++ b/app/src/main/aidl/com/oplus/rom/server/magnifier/IMagnifierService.aidl (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -0,0 +1,24 @@ +package com.oplus.rom.server.magnifier; + +import com.oplus.rom.server.magnifier.IMagnifierCallback; + + +/** +* 游戏助手用系统放大镜接口 +*/ +interface IMagnifierService { + + void registerCallback(IMagnifierCallback callback); + + void unregisterCallback(IMagnifierCallback callback); + + /** + * 显示系统放大镜 + */ + void showMagnifier(float scale, float centerX, float centerY, float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY); + /** + * 隐藏系统放大镜 + */ + void hideMagnifier(); + +} \ No newline at end of file Index: app/src/main/java/com/oplus/rom/server/magnifier/MagnifierService.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/oplus/rom/server/magnifier/MagnifierService.kt b/app/src/main/java/com/oplus/rom/server/magnifier/MagnifierService.kt new file mode 100644 --- /dev/null (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) +++ b/app/src/main/java/com/oplus/rom/server/magnifier/MagnifierService.kt (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -0,0 +1,102 @@ +package com.oplus.rom.server.magnifier + +import android.app.Service +import android.content.Intent +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.os.RemoteCallbackList +import android.view.SurfaceControl +import com.android.internal.graphics.SfVsyncFrameCallbackProvider +import com.oplus.rom.server.ailoop.DebugLog + +class MagnifierService : Service() { + + companion object { + const val TAG = "MagnifierService---" + } + + private var mCallbacks = RemoteCallbackList<IMagnifierCallback>() + + private val mDeathRecipient: IBinder.DeathRecipient = IBinder.DeathRecipient { + DebugLog.e(TAG, "client is dead, stop!") + mHandler.post { + mMagnifierController.deleteWindowMagnification() + stopSelf() + } + } + private val mHandler = Handler(Looper.getMainLooper()) + private val mSfVsyncFrameCallbackProvider = SfVsyncFrameCallbackProvider() + private val mSurfaceTransaction = SurfaceControl.Transaction() + private val mMagnifierController: WindowMagnifierController by lazy { + WindowMagnifierController( + applicationContext, mHandler, mSfVsyncFrameCallbackProvider, mSurfaceTransaction + ) + } + + + private val mMagnifierService = object : IMagnifierService.Stub() { + + override fun registerCallback(callback: IMagnifierCallback?) { + callback?.let { + mCallbacks.register(it) + it.asBinder().linkToDeath(mDeathRecipient, 0) + DebugLog.d(TAG, "registerCallback, size = ${mCallbacks.registeredCallbackCount}") + } + } + + override fun unregisterCallback(callback: IMagnifierCallback?) { + callback?.let { + mCallbacks.unregister(it) + it.asBinder().unlinkToDeath(mDeathRecipient, 0) + DebugLog.d(TAG, "unregisterCallback, size = ${mCallbacks.registeredCallbackCount}") + } + } + + override fun showMagnifier( + scale: Float, + centerX: Float, + centerY: Float, + magnificationFrameOffsetRatioX: Float, + magnificationFrameOffsetRatioY: Float, + ) { + DebugLog.d(TAG, "showMagnifier") + mHandler.post { + mMagnifierController.enableWindowMagnification( + scale, + centerX, + centerY, + magnificationFrameOffsetRatioX, + magnificationFrameOffsetRatioY + ) + } + } + + override fun hideMagnifier() { + DebugLog.d(TAG, "hideMagnifier") + mHandler.post { + mMagnifierController.deleteWindowMagnification() + } + } + } + + override fun onCreate() { + DebugLog.d(TAG, "onCreate") + super.onCreate() + } + + override fun onBind(intent: Intent?): IBinder? { + DebugLog.d(TAG, "onBind, $intent") + return mMagnifierService + } + + override fun onUnbind(intent: Intent?): Boolean { + DebugLog.d(TAG, "onUnbind, $intent") + return super.onUnbind(intent) + } + + override fun onDestroy() { + super.onDestroy() + DebugLog.d(TAG, "onDestroy") + } +} \ No newline at end of file Index: app/src/main/java/com/oplus/rom/server/magnifier/MathUtils.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/oplus/rom/server/magnifier/MathUtils.kt b/app/src/main/java/com/oplus/rom/server/magnifier/MathUtils.kt new file mode 100644 --- /dev/null (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) +++ b/app/src/main/java/com/oplus/rom/server/magnifier/MathUtils.kt (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -0,0 +1,12 @@ +package com.oplus.rom.server.magnifier + +object MathUtils { + fun clamp(value: Int, min: Int, max: Int): Int { + if (value < min) { + return min + } else if (value > max) { + return max + } + return value + } +} \ No newline at end of file Index: app/src/main/java/com/oplus/rom/server/magnifier/WindowMagnifierController.kt IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/java/com/oplus/rom/server/magnifier/WindowMagnifierController.kt b/app/src/main/java/com/oplus/rom/server/magnifier/WindowMagnifierController.kt new file mode 100644 --- /dev/null (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) +++ b/app/src/main/java/com/oplus/rom/server/magnifier/WindowMagnifierController.kt (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -0,0 +1,637 @@ +package com.oplus.rom.server.magnifier + +import android.content.ComponentCallbacks +import android.content.Context +import android.content.pm.ActivityInfo +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.content.res.Resources +import android.graphics.Matrix +import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.RectF +import android.os.Handler +import android.os.RemoteException +import android.util.Log +import android.util.Size +import android.view.Choreographer.FrameCallback +import android.view.Gravity +import android.view.LayoutInflater +import android.view.Surface +import android.view.SurfaceControl +import android.view.SurfaceHolder +import android.view.SurfaceView +import android.view.View +import android.view.View.OnLayoutChangeListener +import android.view.WindowInsets +import android.view.WindowManager +import android.view.WindowManagerGlobal +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.graphics.SfVsyncFrameCallbackProvider +import com.oplus.rom.server.R +import com.oplus.rom.server.ailoop.DebugLog +import java.text.NumberFormat +import java.util.Locale +import kotlin.math.max +import kotlin.math.min + +/** + * 游戏助手用系统放大镜 + */ +class WindowMagnifierController( + private val mContext: Context, + private val mHandler: Handler, + private val mSfVsyncFrameProvider: SfVsyncFrameCallbackProvider, + private val mTransaction: SurfaceControl.Transaction, +) : SurfaceHolder.Callback, ComponentCallbacks { + + companion object { + private const val TAG = "WindowMagnifierController" + private const val DEFAULT_SCALE = 2.0F + } + + private val mResources: Resources + private val mWindowBounds: Rect + private val mDisplayId: Int + + @Surface.Rotation + @VisibleForTesting + var mRotation: Int + private val mWm: WindowManager? + + private var mScale = DEFAULT_SCALE + private val mMagnificationFrame = Rect() + private val mTmpRect = Rect() + + private val mMirrorViewBounds = Rect() + + private val mSourceBounds = Rect() + + private var mMagnificationFrameOffsetX = 0 + private var mMagnificationFrameOffsetY = 0 + + private var mMirrorSurface: SurfaceControl? = null + + private val mConfiguration: Configuration + + private val mMirrorViewLayoutChangeListener: OnLayoutChangeListener + private val mMirrorViewRunnable: Runnable + private val mUpdateStateDescriptionRunnable: Runnable + private val mWindowInsetChangeRunnable: Runnable + + private var mMirrorView: View? = null + private var mMirrorSurfaceView: SurfaceView? = null + + private val mMagnificationFrameBoundary = Rect() + + private var mSystemGestureTop = -1 + private var mMinWindowSize = 0 + + private val mMirrorViewGeometryVsyncCallback: FrameCallback? + private var mLocale: Locale? = null + private var mPercentFormat: NumberFormat? = null + + init { + mConfiguration = Configuration(mContext.resources.configuration) + val display = mContext.display + mDisplayId = mContext.displayId + DebugLog.d(TAG, "mDisplayId = $mDisplayId") + mRotation = display.rotation + mWm = mContext.getSystemService<WindowManager>(WindowManager::class.java) + mWindowBounds = Rect(mWm?.currentWindowMetrics?.bounds) + + mResources = mContext.resources + + val windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds) + setMagnificationFrame( + windowSize.width, + windowSize.height, + mWindowBounds.width() / 2, + mWindowBounds.height() / 2 + ) + // Initialize listeners. + mMirrorViewRunnable = Runnable { + if (mMirrorView != null) { + val oldViewBounds = Rect(mMirrorViewBounds) + mMirrorView?.getBoundsOnScreen(mMirrorViewBounds) + if (oldViewBounds.width() != mMirrorViewBounds.width() || oldViewBounds.height() != mMirrorViewBounds.height()) { + mMirrorView?.setSystemGestureExclusionRects( + mutableListOf<Rect?>( + Rect(0, 0, mMirrorViewBounds.width(), mMirrorViewBounds.height()) + ) + ) + } + } + } + mMirrorViewLayoutChangeListener = + OnLayoutChangeListener { v: View?, left: Int, top: Int, right: Int, bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int -> + if (!mHandler.hasCallbacks(mMirrorViewRunnable)) { + mHandler.post(mMirrorViewRunnable) + } + } + + + mMirrorViewGeometryVsyncCallback = FrameCallback { l: Long -> + if (this.isWindowVisible && mMirrorSurface != null && calculateSourceBounds( + mMagnificationFrame, mScale + ) + ) { + mTmpRect.set( + 0, 0, mMagnificationFrame.width(), mMagnificationFrame.height() + ) + mTransaction.setGeometry( + mMirrorSurface!!, mSourceBounds, mTmpRect, Surface.ROTATION_0 + ).apply() + + } + } + mUpdateStateDescriptionRunnable = Runnable { + if (this.isWindowVisible) { + mMirrorView?.setStateDescription(formatStateDescription(mScale)) + } + } + mWindowInsetChangeRunnable = Runnable { this.onWindowInsetChanged() } + } + + private fun updateSystemGestureInsetsTop(): Boolean { + val windowMetrics = mWm?.currentWindowMetrics + val insets = windowMetrics?.getWindowInsets()?.getInsets(WindowInsets.Type.systemGestures()) + val gestureTop = + if (insets?.bottom != 0) { + windowMetrics?.bounds?.bottom ?: 0 - (insets?.bottom ?: 0) + } else -1 + if (gestureTop != mSystemGestureTop) { + mSystemGestureTop = gestureTop + return true + } + return false + } + + fun deleteWindowMagnification() { + if (!this.isWindowVisible) { + return + } + if (mMirrorSurface != null) { + mTransaction.remove(mMirrorSurface!!).apply() + mMirrorSurface = null + } + + if (mMirrorView != null) { + mHandler.removeCallbacks(mMirrorViewRunnable) + mMirrorView?.removeOnLayoutChangeListener(mMirrorViewLayoutChangeListener) + mWm?.removeView(mMirrorView) + mMirrorView = null + } + + mMirrorViewBounds.setEmpty() + mSourceBounds.setEmpty() + mContext.unregisterComponentCallbacks(this) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + val configDiff = newConfig.diff(mConfiguration) + mConfiguration.setTo(newConfig) + onConfigurationChanged(configDiff) + } + + @Deprecated("Deprecated in Java") + override fun onLowMemory() { + } + + fun onConfigurationChanged(configDiff: Int) { + DebugLog.d( + TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString( + configDiff + ) + ) + if (configDiff == 0) { + return + } + if ((configDiff and ActivityInfo.CONFIG_ORIENTATION) != 0) { + onRotate() + } + + if ((configDiff and ActivityInfo.CONFIG_LOCALE) != 0) { + updateAccessibilityWindowTitleIfNeeded() + } + + var reCreateWindow = false + if ((configDiff and ActivityInfo.CONFIG_DENSITY) != 0) { + reCreateWindow = true + } + + if ((configDiff and ActivityInfo.CONFIG_SCREEN_SIZE) != 0) { + reCreateWindow = reCreateWindow or handleScreenSizeChanged() + } + + // Recreate the window again to correct the window appearance due to density or + // window size changed not caused by rotation. + if (this.isWindowVisible && reCreateWindow) { + deleteWindowMagnification() + enableWindowMagnificationInternal( + Float.Companion.NaN, Float.Companion.NaN, Float.Companion.NaN + ) + } + } + + private fun handleScreenSizeChanged(): Boolean { + val oldWindowBounds = Rect(mWindowBounds) + val currentWindowBounds = mWm?.currentWindowMetrics?.bounds + + if (currentWindowBounds == oldWindowBounds) { + DebugLog.d(TAG, "handleScreenSizeChanged -- window bounds is not changed") + return false + } + currentWindowBounds?.let { + mWindowBounds.set(it) + } + val windowSize = getDefaultWindowSizeWithWindowBounds(mWindowBounds) + val newCenterX = (this.centerX) * mWindowBounds.width() / oldWindowBounds.width() + val newCenterY = (this.centerY) * mWindowBounds.height() / oldWindowBounds.height() + setMagnificationFrame( + windowSize.width, windowSize.height, newCenterX.toInt(), newCenterY.toInt() + ) + calculateMagnificationFrameBoundary() + return true + } + + private fun updateSystemUIStateIfNeeded() { + } + + private fun updateAccessibilityWindowTitleIfNeeded() { + if (!this.isWindowVisible) return + val params = mMirrorView?.layoutParams as WindowManager.LayoutParams + mWm?.updateViewLayout(mMirrorView, params) + } + + private fun onRotate() { + val display = mContext.display + val oldRotation = mRotation + mRotation = display.rotation + val rotationDegree = getDegreeFromRotation(mRotation, oldRotation) + if (rotationDegree == 0 || rotationDegree == 180) { + DebugLog.w(TAG, "onRotate -- rotate with the device. skip it") + return + } + val currentWindowBounds = Rect(mWm?.currentWindowMetrics?.bounds) + if (currentWindowBounds.width() != mWindowBounds.height() || currentWindowBounds.height() != mWindowBounds.width()) { + DebugLog.w(TAG, "onRotate -- unexpected window height/width") + return + } + + mWindowBounds.set(currentWindowBounds) + + // Keep MirrorWindow position on the screen unchanged when device rotates 90° + // clockwise or anti-clockwise. + val matrix = Matrix() + matrix.setRotate(rotationDegree.toFloat()) + if (rotationDegree == 90) { + matrix.postTranslate(mWindowBounds.width().toFloat(), 0f) + } else if (rotationDegree == 270) { + matrix.postTranslate(0f, mWindowBounds.height().toFloat()) + } + + val transformedRect = RectF(mMagnificationFrame) + transformedRect.inset(0.0F, 0.0F) + matrix.mapRect(transformedRect) + setWindowSizeAndCenter( + transformedRect.width().toInt(), + transformedRect.height().toInt(), + transformedRect.centerX().toInt().toFloat(), + transformedRect.centerY().toInt().toFloat() + ) + } + + private fun getDegreeFromRotation( + @Surface.Rotation newRotation: Int, + @Surface.Rotation oldRotation: Int, + ): Int { + val rotationDiff = oldRotation - newRotation + val degree = (rotationDiff + 4) % 4 * 90 + return degree + } + + private fun createMirrorWindow() { + // The window should be the size the mirrored surface will be but also add room for the + // border and the drag handle. + val windowWidth = mMagnificationFrame.width() + val windowHeight = mMagnificationFrame.height() + + val params = WindowManager.LayoutParams( + windowWidth, + windowHeight, + WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSPARENT + ) + params.gravity = Gravity.TOP or Gravity.LEFT + params.x = mMagnificationFrame.left + params.y = mMagnificationFrame.top + params.layoutInDisplayCutoutMode = + WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + params.receiveInsetsIgnoringZOrder = true + params.setTitle(mContext.getString(R.string.magnification_window_title)) + mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null) + mMirrorView?.layoutParams = params + mMirrorSurfaceView = mMirrorView?.findViewById<SurfaceView?>(R.id.surface_view) + mMirrorView?.setSystemUiVisibility( + (View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) + ) + mMirrorSurfaceView?.setCornerRadius( + mResources.getDimensionPixelOffset(R.dimen.magnification_radius).toFloat() + ) + mMirrorView?.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener) + mMirrorView?.setOnApplyWindowInsetsListener(View.OnApplyWindowInsetsListener { v: View?, insets: WindowInsets? -> + if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) { + mHandler.post(mWindowInsetChangeRunnable) + } + //todo + v!!.onApplyWindowInsets(insets) + }) + mWm?.addView(mMirrorView, params) + val holder = mMirrorSurfaceView?.holder + holder?.addCallback(this) + holder?.setFormat(PixelFormat.RGBA_8888) + } + + private fun onWindowInsetChanged() { + if (updateSystemGestureInsetsTop()) { + updateSystemUIStateIfNeeded() + } + } + + fun setWindowSizeAndCenter(width: Int, height: Int, centerX: Float, centerY: Float) { + var width = width + var height = height + var centerX = centerX + var centerY = centerY + width = MathUtils.clamp(width, mMinWindowSize, mWindowBounds.width()) + height = MathUtils.clamp(height, mMinWindowSize, mWindowBounds.height()) + + if (java.lang.Float.isNaN(centerX)) { + centerX = mMagnificationFrame.centerX().toFloat() + } + if (java.lang.Float.isNaN(centerX)) { + centerY = mMagnificationFrame.centerY().toFloat() + } + + val frameWidth = width + val frameHeight = height + setMagnificationFrame(frameWidth, frameHeight, centerX.toInt(), centerY.toInt()) + calculateMagnificationFrameBoundary() + updateMagnificationFramePosition(0, 0) + modifyWindowMagnification(true) + } + + private fun setMagnificationFrame(width: Int, height: Int, centerX: Int, centerY: Int) { + val initX = centerX - width / 2 + val initY = centerY - height / 2 + mMagnificationFrame.set(initX, initY, initX + width, initY + height) + } + + private fun getDefaultWindowSizeWithWindowBounds(windowBounds: Rect): Size { + var initSize = mResources.getDimensionPixelSize(R.dimen.magnification_max_frame_size) + return Size(initSize, initSize) + } + + private fun createMirror() { + mMirrorSurface = createMirrorSurface() + if (false == mMirrorSurface?.isValid) { + return + } + mMirrorSurface?.let { + mTransaction.show(it) + .reparent(it, mMirrorSurfaceView?.surfaceControl) + modifyWindowMagnification(false) + } + } + + private fun createMirrorSurface(): SurfaceControl? { + try { + val outSurfaceControl = SurfaceControl() + WindowManagerGlobal.getWindowManagerService()?.mirrorDisplay( + mDisplayId, + outSurfaceControl + ) + return outSurfaceControl + } catch (e: RemoteException) { + Log.e(TAG, "Unable to reach window manager", e) + return null + } + } + + private fun modifyWindowMagnification(computeWindowSize: Boolean) { + mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback) + updateMirrorViewLayout(computeWindowSize) + } + + private fun updateMirrorViewLayout(computeWindowSize: Boolean) { + if (!this.isWindowVisible) { + return + } + val maxMirrorViewX = mMirrorView?.width?.let { mWindowBounds.width() - it } ?: 0 + val maxMirrorViewY = mMirrorView?.height?.let { mWindowBounds.height() - it } ?: 0 + + val params = mMirrorView?.layoutParams as WindowManager.LayoutParams + params.x = mMagnificationFrame.left + params.y = mMagnificationFrame.top + if (computeWindowSize) { + params.width = mMagnificationFrame.width() + params.height = mMagnificationFrame.height() + } + val translationX: Float + val translationY: Float + if (params.x < 0) { + translationX = max(params.x, 0).toFloat() + } else if (params.x > maxMirrorViewX) { + translationX = min(params.x - maxMirrorViewX, 0).toFloat() + } else { + translationX = 0f + } + if (params.y < 0) { + translationY = max(params.y, -0).toFloat() + } else if (params.y > maxMirrorViewY) { + translationY = min(params.y - maxMirrorViewY, 0).toFloat() + } else { + translationY = 0f + } + mMirrorView?.translationX = translationX + mMirrorView?.translationY = translationY + mWm?.updateViewLayout(mMirrorView, params) + } + + private fun calculateSourceBounds(displayFrame: Rect, scale: Float): Boolean { + val oldSourceBounds = mTmpRect + oldSourceBounds.set(mSourceBounds) + val halfWidth = displayFrame.width() / 2 + val halfHeight = displayFrame.height() / 2 + val left = displayFrame.left + (halfWidth - (halfWidth / scale).toInt()) + val right = displayFrame.right - (halfWidth - (halfWidth / scale).toInt()) + val top = displayFrame.top + (halfHeight - (halfHeight / scale).toInt()) + val bottom = displayFrame.bottom - (halfHeight - (halfHeight / scale).toInt()) + mSourceBounds.set(left, top, right, bottom) + mSourceBounds.offset(-mMagnificationFrameOffsetX, -mMagnificationFrameOffsetY) + if (mSourceBounds.left < 0) { + mSourceBounds.offsetTo(0, mSourceBounds.top) + } else if (mSourceBounds.right > mWindowBounds.width()) { + mSourceBounds.offsetTo( + mWindowBounds.width() - mSourceBounds.width(), mSourceBounds.top + ) + } + if (mSourceBounds.top < 0) { + mSourceBounds.offsetTo(mSourceBounds.left, 0) + } else if (mSourceBounds.bottom > mWindowBounds.height()) { + mSourceBounds.offsetTo( + mSourceBounds.left, mWindowBounds.height() - mSourceBounds.height() + ) + } + return mSourceBounds != oldSourceBounds + } + + private fun calculateMagnificationFrameBoundary() { + // The half width of magnification frame. + val halfWidth = mMagnificationFrame.width() / 2 + // The half height of magnification frame. + val halfHeight = mMagnificationFrame.height() / 2 + // The scaled half width of magnified region. + val scaledWidth = (halfWidth / mScale).toInt() + // The scaled half height of magnified region. + val scaledHeight = (halfHeight / mScale).toInt() + val exceededLeft = max(halfWidth - scaledWidth - mMagnificationFrameOffsetX, 0) + val exceededRight = max(halfWidth - scaledWidth + mMagnificationFrameOffsetX, 0) + val exceededTop = max(halfHeight - scaledHeight - mMagnificationFrameOffsetY, 0) + val exceededBottom = max( + halfHeight - scaledHeight + mMagnificationFrameOffsetY, 0 + ) + DebugLog.d(TAG, "calculateMagnificationFrameBoundary, halfWidth = $halfWidth") + DebugLog.d(TAG, "calculateMagnificationFrameBoundary, halfHeight = $halfHeight") + DebugLog.d(TAG, "calculateMagnificationFrameBoundary, scaledWidth = $scaledWidth") + DebugLog.d(TAG, "calculateMagnificationFrameBoundary, scaledHeight = $scaledHeight") + DebugLog.d(TAG, "calculateMagnificationFrameBoundary, exceededLeft = $exceededLeft") + DebugLog.d(TAG, "calculateMagnificationFrameBoundary, exceededRight = $exceededRight") + DebugLog.d(TAG, "calculateMagnificationFrameBoundary, exceededTop = $exceededTop") + DebugLog.d(TAG, "calculateMagnificationFrameBoundary, exceededBottom = $exceededBottom") + mMagnificationFrameBoundary.set( + -exceededLeft, + -exceededTop, + mWindowBounds.width() + exceededRight, + mWindowBounds.height() + exceededBottom + ) + DebugLog.d(TAG, "calculateMagnificationFrameBoundary, $mMagnificationFrameBoundary") + } + + private fun updateMagnificationFramePosition(xOffset: Int, yOffset: Int): Boolean { + mTmpRect.set(mMagnificationFrame) + mTmpRect.offset(xOffset, yOffset) + + if (mTmpRect.left < mMagnificationFrameBoundary.left) { + mTmpRect.offsetTo(mMagnificationFrameBoundary.left, mTmpRect.top) + } else if (mTmpRect.right > mMagnificationFrameBoundary.right) { + val leftOffset = mMagnificationFrameBoundary.right - mMagnificationFrame.width() + mTmpRect.offsetTo(leftOffset, mTmpRect.top) + } + + if (mTmpRect.top < mMagnificationFrameBoundary.top) { + mTmpRect.offsetTo(mTmpRect.left, mMagnificationFrameBoundary.top) + } else if (mTmpRect.bottom > mMagnificationFrameBoundary.bottom) { + val topOffset = mMagnificationFrameBoundary.bottom - mMagnificationFrame.height() + mTmpRect.offsetTo(mTmpRect.left, topOffset) + } + + if (mTmpRect != mMagnificationFrame) { + mMagnificationFrame.set(mTmpRect) + return true + } + return false + } + + fun enableWindowMagnification( + scale: Float, centerX: Float, centerY: Float, + magnificationFrameOffsetRatioX: Float, magnificationFrameOffsetRatioY: Float, + ) { + enableWindowMagnificationInternal( + scale, + centerX, + centerY, + magnificationFrameOffsetRatioX, + magnificationFrameOffsetRatioY + ) + } + + @JvmOverloads + fun enableWindowMagnificationInternal( + scale: Float, + centerX: Float, + centerY: Float, + magnificationFrameOffsetRatioX: Float = Float.Companion.NaN, + magnificationFrameOffsetRatioY: Float = Float.Companion.NaN, + ) { + if (scale <= 1.0F) { + deleteWindowMagnification() + return + } + if (!this.isWindowVisible) { + onConfigurationChanged(mResources.configuration) + mContext.registerComponentCallbacks(this) + } + + mMagnificationFrameOffsetX = + if (Float.NaN == magnificationFrameOffsetRatioX) mMagnificationFrameOffsetX + else (mMagnificationFrame.width() / 2 * magnificationFrameOffsetRatioX).toInt() + mMagnificationFrameOffsetY = + if (Float.NaN == magnificationFrameOffsetRatioY) mMagnificationFrameOffsetY + else (mMagnificationFrame.height() / 2 * magnificationFrameOffsetRatioY).toInt() + + // The relation of centers between SourceBound and MagnificationFrame is as following: + // MagnificationFrame = SourceBound (e.g., centerX & centerY) + MagnificationFrameOffset + val newMagnificationFrameCenterX = centerX + mMagnificationFrameOffsetX + val newMagnificationFrameCenterY = centerY + mMagnificationFrameOffsetY + + val offsetX = if (Float.NaN == centerX) 0f + else newMagnificationFrameCenterX - mMagnificationFrame.exactCenterX() + val offsetY = if (Float.NaN == centerY) 0f + else newMagnificationFrameCenterY - mMagnificationFrame.exactCenterY() + mScale = if (Float.NaN == scale) mScale else scale + calculateMagnificationFrameBoundary() + updateMagnificationFramePosition(offsetX.toInt(), offsetY.toInt()) + if (!this.isWindowVisible) { + createMirrorWindow() + } else { + modifyWindowMagnification(false) + } + } + + val centerX: Float + get() = if (this.isWindowVisible) mMagnificationFrame.exactCenterX() else Float.Companion.NaN + + val centerY: Float + get() = if (this.isWindowVisible) mMagnificationFrame.exactCenterY() else Float.Companion.NaN + + private val isWindowVisible: Boolean + get() = mMirrorView != null + + private fun formatStateDescription(scale: Float): CharSequence? { + val curLocale = mContext.resources.configuration.getLocales().get(0) + if (curLocale != mLocale) { + mLocale = curLocale + mPercentFormat = NumberFormat.getPercentInstance(curLocale) + } + return mPercentFormat?.format(scale.toDouble()) + } + + override fun surfaceCreated(holder: SurfaceHolder) { + createMirror() + } + + override fun surfaceChanged( + holder: SurfaceHolder, + format: Int, + width: Int, + height: Int, + ) { + } + + override fun surfaceDestroyed(holder: SurfaceHolder) { + } +} \ No newline at end of file Index: app/src/main/res/drawable/bg_magnifier.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/res/drawable/bg_magnifier.xml b/app/src/main/res/drawable/bg_magnifier.xml new file mode 100644 --- /dev/null (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) +++ b/app/src/main/res/drawable/bg_magnifier.xml (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:innerRadius="@dimen/magnification_radius" + android:useLevel="false" + android:shape="ring" + android:thickness="1px"> + <stroke android:color="@color/white" android:width="1px"/> +</shape> \ No newline at end of file Index: app/src/main/res/layout/window_magnifier_view.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/res/layout/window_magnifier_view.xml b/app/src/main/res/layout/window_magnifier_view.xml new file mode 100644 --- /dev/null (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) +++ b/app/src/main/res/layout/window_magnifier_view.xml (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="@dimen/magnification_max_frame_size" + android:background="@color/color_transparent" + android:layout_height="@dimen/magnification_max_frame_size"> + + <SurfaceView + android:id="@+id/surface_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@drawable/bg_magnifier"/> +</FrameLayout> \ No newline at end of file Index: app/src/main/res/values/colors.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml --- a/app/src/main/res/values/colors.xml (revision 87afe212d78a0d91635bca6e882a6fa407331bc5) +++ b/app/src/main/res/values/colors.xml (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -12,5 +12,7 @@ <color name="coloros_ep_setting_description_title_color">#E5000000</color> <color name="coloros_ep_setting_description_text_color">#8A000000</color> + <color name="color_transparent">#00000000</color> + </resources> \ No newline at end of file Index: app/src/main/res/values/dimens.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml --- a/app/src/main/res/values/dimens.xml (revision 87afe212d78a0d91635bca6e882a6fa407331bc5) +++ b/app/src/main/res/values/dimens.xml (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -9,4 +9,6 @@ <dimen name="preference_headerView_height_bottom">62dp</dimen> <dimen name="speaker_clean_icon_anim">407dp</dimen> <dimen name="speaker_clean_icon_anim_land">250dp</dimen> + <dimen name="magnification_max_frame_size">200dp</dimen> + <dimen name="magnification_radius">100dp</dimen> </resources> \ No newline at end of file Index: app/src/main/res/values/strings.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml --- a/app/src/main/res/values/strings.xml (revision 87afe212d78a0d91635bca6e882a6fa407331bc5) +++ b/app/src/main/res/values/strings.xml (revision ee8a0cf2a6cbd12e83e0d4b0e09f0eaacaa6e4d8) @@ -4,4 +4,5 @@ <string name="app_name">RomServer</string> <string name="toast_docment_drag_send">You can preview the document and drag & drop it to share</string> <string name="app_name_new">Basic system services</string> + <string name="magnification_window_title" translatable="false">magnifier</string> </resources> Index: app/src/main/res/layout/main_layout.xml =================================================================== diff --git a/app/src/main/res/layout/main_layout.xml b/app/src/main/res/layout/main_layout.xml deleted file mode 100644 --- a/app/src/main/res/layout/main_layout.xml (revision 87afe212d78a0d91635bca6e882a6fa407331bc5) +++ /dev/null (revision 87afe212d78a0d91635bca6e882a6fa407331bc5) @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/rootView" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fitsSystemWindows="false" /> \ No newline at end of file
11-19
package com.example.hlm6.service; import static android.content.ContentValues.TAG; import android.annotation.SuppressLint; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; public class BLEService extends Service { private BluetoothAdapter mBluetoothAdapter = null; private BluetoothDevice curBluetoothDevice = null; //当前连接的设备 public static BLEManager bleManager; private static StringBuilder weighRecord; public static final String SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb"; //蓝牙通讯服务 public static final String READ_UUID = "0000ffe1-0000-1000-8000-00805f9b34fb"; //读特征 public static final String WRITE_UUID = "0000ffe2-0000-1000-8000-00805f9b34fb"; //写特征 private static final int CONNECT_SUCCESS = 0x01; private static final int RECEIVE_SUCCESS = 0x02; private static final int CONNECT_DIS = 0x03; private static final int CONNECT_Failure = 0x04; private boolean curConnState = false;//当前连接状态 private String bleAddress; private Intent intentHomeActivity = new Intent("hl.example.hlbluetoothapp.homeActivity"); // 添加广播Action常量 public static final String ACTION_BLE_CONNECTED = "com.example.hlm6.ACTION_BLE_CONNECTED"; public static final String ACTION_BLE_DISCONNECTED = "com.example.hlm6.ACTION_BLE_DISCONNECTED"; public static final String EXTRA_DEVICE_ADDRESS = "device_address"; public static final String EXTRA_DEVICE_NAME = "device_name"; @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null) { Bundle bundle = intent.getExtras(); if (bundle != null) { bleAddress = bundle.getString("bleAddress"); initializeBluetooth(bleAddress); } } weighRecord = new StringBuilder(); return super.onStartCommand(intent, flags, startId); } @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); switch (msg.what) { case CONNECT_SUCCESS: Log.d(TAG, "连接成功..."); Integer status3 = (Integer) msg.obj; intentHomeActivity.putExtra("status", status3); sendBroadcast(intentHomeActivity); break; case RECEIVE_SUCCESS: break; case CONNECT_DIS: Integer status1 = (Integer) msg.obj; intentHomeActivity.putExtra("status", status1); sendBroadcast(intentHomeActivity); break; case CONNECT_Failure: Integer status2 = (Integer) msg.obj; intentHomeActivity.putExtra("status", status2); sendBroadcast(intentHomeActivity); break; } } }; //初始化蓝牙连接 public boolean initializeBluetooth(String bleAddress) { Log.d(TAG, "initializeBluetooth : bleAddress " + bleAddress); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { Log.e(TAG, "设备不支持蓝牙"); return false; } try { curBluetoothDevice = mBluetoothAdapter.getRemoteDevice(bleAddress); } catch (IllegalArgumentException e) { Log.e(TAG, "蓝牙地址不正确"); return false; } bleManager = new BLEManager(); btConnect(curBluetoothDevice); // 传递curBluetoothDevice return true; } /// /////////////////////////////// 连接蓝牙 ///////////////////////////////////////////// public void btConnect(BluetoothDevice bluetoothDevice) { Log.d(TAG, "btConnect: 开始连接蓝牙 "+curConnState); if (!curConnState) { Log.d(TAG, "btConnect: 开始连接蓝牙 测试"+bleManager); if (bleManager != null) { bleManager.connectBleDevice(BLEService.this, bluetoothDevice, 10000, SERVICE_UUID, READ_UUID, WRITE_UUID, onBleConnectListener); } } else { Log.d(TAG, "btConnect:当前设备已连接 "); } Log.d(TAG, "btConnect: 连接蓝牙结束"); } //连接回调 private OnBleConnectListener onBleConnectListener = new OnBleConnectListener() { @Override public void onConnecting(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice) { //正在连接 } @Override public void onConnectSuccess(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice, int status) { //因为服务发现成功之后,才能通讯,所以在成功发现服务的地方表示连接成功,不在此表示连接成功 System.out.println("因为服务发现成功之后,才能通讯,所以在成功发现服务的地方表示连接成功,不在此表示连接成功"); } @Override public void onConnectFailure(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice, String exception, int status) { //连接失败 Message message = new Message(); message.what = CONNECT_Failure; message.obj = status; mHandler.sendMessage(message); } @Override public void onDisConnecting(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice) { //正在断开 } @Override public void onDisConnectSuccess(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice, int status) { System.out.println("断开连接"); // 断开连接 Message message = new Message(); message.what = CONNECT_DIS; message.obj = status; mHandler.sendMessage(message); curConnState = false; // 设置连接状态为断开 } @Override public void onServiceDiscoverySucceed(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice, int status) { //因为服务发现成功之后,才能通讯,所以在成功发现服务的地方表示连接成功 System.out.println("因为服务发现成功之后,才能通讯,所以在成功发现服务的地方表示连接成功"); Message message = new Message(); message.what = CONNECT_SUCCESS; message.obj = -2; mHandler.sendMessage(message); curConnState = true; // 设置连接状态为已连接 // 发送连接成功广播 Intent connectedIntent = new Intent(ACTION_BLE_CONNECTED); connectedIntent.putExtra(EXTRA_DEVICE_ADDRESS, bluetoothDevice.getAddress()); connectedIntent.putExtra(EXTRA_DEVICE_NAME, bluetoothDevice.getName()); sendBroadcast(connectedIntent); } @Override public void onServiceDiscoveryFailed(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice, String failMsg) { //发现服务失败 } @Override public void onReceiveMessage(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice, BluetoothGattCharacteristic characteristic, byte[] msg) { //收到消息 Message message = new Message(); message.what = RECEIVE_SUCCESS; message.obj = msg; mHandler.sendMessage(message); } @Override public void onReceiveError(String errorMsg) { //接收数据出错 } @Override public void onWriteSuccess(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice, byte[] msg) { //写入成功 } @Override public void onWriteFailure(BluetoothGatt bluetoothGatt, BluetoothDevice bluetoothDevice, byte[] msg, String errorMsg) { //写入失败 } @Override public void onReadRssi(BluetoothGatt bluetoothGatt, int Rssi, int status) { //成功读取到连接信号强度 } @Override public void onMTUSetSuccess(String successMTU, int newMtu) { //MTU设置成功 } @Override public void onMTUSetFailure(String failMTU) { //MTU设置失败 } }; } package com.example.hlm6.controller; import static android.content.ContentValues.TAG; import android.annotation.SuppressLint; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import android.os.Handler; import android.os.Message; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.Toast; import com.example.hlm6.R; import com.example.hlm6.adapter.LVDevicesAdapter; import com.example.hlm6.adapter.OnDeviceClickListener; import com.example.hlm6.service.BLEDevice; import com.example.hlm6.service.BLEManager; import com.example.hlm6.service.BLEService; import com.example.hlm6.service.OnBleConnectListener; import com.example.hlm6.service.OnDeviceSearchListener; public class MessageFragment extends Fragment implements View.OnClickListener { private static final String ARG_PARAM1 = "param1"; private static final String ARG_PARAM2 = "param2"; private LVDevicesAdapter lvDevicesAdapter; private BLEManager bleManager; private Button btSearchBluetooth; private LinearLayout llDeviceList; private ListView lvDevices; private boolean curConnState = false; private String address; private String mParam1; private String mParam2; private static final int CONNECT_SUCCESS = 0x01; private static final int RECEIVE_SUCCESS = 0x02; private static final int DISCOVERY_DEVICE = 0x05; public MessageFragment() { } public static MessageFragment newInstance(String param1, String param2) { MessageFragment fragment = new MessageFragment(); Bundle args = new Bundle(); args.putString(ARG_PARAM1, param1); args.putString(ARG_PARAM2, param2); fragment.setArguments(args); return fragment; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); IntentFilter filter = new IntentFilter(); filter.addAction("hl.example.hlbluetoothapp.homeActivity"); requireActivity().registerReceiver(mBroadcastReceiver, filter); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_message, container, false); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { Log.d(TAG, "onViewCreated: -------初始化onViewCreated-------"); super.onViewCreated(view, savedInstanceState); //初始化视图 btSearchBluetooth = view.findViewById(R.id.bt_searchBluetooth); llDeviceList = view.findViewById(R.id.ll_device_list); lvDevices = view.findViewById(R.id.lv_devices); //初始化数据 initData(); //初始化监听 btSearchBluetooth.setOnClickListener(this); } private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if ("hl.example.hlbluetoothapp.homeActivity".equals(action)) { int status = intent.getIntExtra("status", -1); if (status == -2) { // 连接成功,切换到称重页面 System.out.println("连接成功,切换到称重页面"); toWeighFragment(); } } } }; @Override public void onDestroy() { super.onDestroy(); requireActivity().unregisterReceiver(mBroadcastReceiver); } @Override public void onClick(View view) { if (view.getId() == R.id.bt_searchBluetooth) { System.out.println("onClick R.id.bt_search 点击开始搜索蓝牙"); llDeviceList.setVisibility(View.VISIBLE);//显示llDeviceList布局 searchBtDevice(); } } /** * 初始化数据 */ private void initData() { FragmentActivity activity = getActivity(); System.out.println("初始化数据"); //列表适配器 lvDevicesAdapter = new LVDevicesAdapter(getActivity()); //绘制列表适配器 lvDevices.setAdapter(lvDevicesAdapter); //初始化ble管理器 bleManager.initBle判断设备是否支持蓝牙,设备支持蓝牙后用bleManager.openBluetooth开启设备蓝牙。 bleManager = new BLEManager(); if (!bleManager.initBle(activity)) { Log.d(TAG, "该设备不支持低功耗蓝牙"); Toast.makeText(activity, "该设备不支持低功耗蓝牙", Toast.LENGTH_SHORT).show(); } else { if (!bleManager.isEnable()) { //去打开蓝牙 bleManager.openBluetooth(activity, false); } } System.out.println("初始化数据结束"); } /// /////////////////////////////// 搜索设备 ///////////////////////////////////////////// private void searchBtDevice() { System.out.println("搜索设备"); if (bleManager == null) { Log.d(TAG, "searchBtDevice()-->bleManager == null"); return; } //判断是否正在扫描,如果当前正在搜索设备,先停止搜索 if (bleManager.isDiscovery()) { bleManager.stopDiscoveryDevice(); } //判读蓝牙列表是否为空 if (lvDevicesAdapter != null) { lvDevicesAdapter.clear(); //清空列表 } //开始搜索 bleManager.startDiscoveryDevice(onDeviceSearchListener, 1000); } //扫描结果回调 private OnDeviceSearchListener onDeviceSearchListener = new OnDeviceSearchListener() { @Override public void onDeviceFound(BLEDevice bleDevice) { System.out.println("扫描结果回调 onDeviceFound "); Message message = new Message(); message.what = DISCOVERY_DEVICE; message.obj = bleDevice; mHandler.sendMessage(message); } @Override public void onDiscoveryOutTime() { } }; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @SuppressLint("SetTextI18n") @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); switch (msg.what) { case DISCOVERY_DEVICE://扫描到设备 System.out.println("handle 扫描到设备"); BLEDevice bleDevice = (BLEDevice) msg.obj; lvDevicesAdapter.addDevice(bleDevice); lvDevicesAdapter.setOnDeviceClickListener(new OnDeviceClickListener() { @Override public void onConnect(BLEDevice bleDevice) { BluetoothDevice bluetoothDevice = bleDevice.getBluetoothDevice(); address = bluetoothDevice.getAddress(); //启动服务,传递蓝牙地址,连接蓝牙 Bundle bundle = new Bundle(); bundle.putString("bleAddress", address); Intent serviceIntent = new Intent(getActivity(), BLEService.class); serviceIntent.putExtras(bundle); //需判断蓝牙是否连接成功 getActivity().startService(serviceIntent); // 不再立即跳转,等待连接成功后再跳转 // 可以显示一个加载对话框或提示用户正在连接 //showConnectingDialog(); } }); break; } } }; private void toWeighFragment(){ // 创建weighFragment的实例 WeighFragment weighFragment = new WeighFragment(); // 获取FragmentManager FragmentManager fragmentManager = getActivity().getSupportFragmentManager(); // 开启事务 FragmentTransaction transaction = fragmentManager.beginTransaction(); // 替换Fragment,其中R.id.fcv_operation是放置Fragment的容器 transaction.replace(R.id.fcv_operation, weighFragment); // 可选:添加到返回栈,这样按返回键可以回到上一个Fragment transaction.addToBackStack(null); // 提交事务 transaction.commit(); } } 在MessageFragment搜索蓝牙列表,并点击指定蓝牙进行连接。总是失败,分析原因 2025-09-19 16:27:54.789 13765-13765 ContentValues com.example.hlm6 D btConnect: 开始连接蓝牙 false 2025-09-19 16:27:54.789 13765-13765 ContentValues com.example.hlm6 D btConnect: 开始连接蓝牙 测试com.example.hlm6.service.BLEManager@7424b22 2025-09-19 16:27:54.789 13765-13765 BLEManager com.example.hlm6 D connectBleDevice: --------------通过蓝牙设备连接------------ 2025-09-19 16:27:54.790 13765-13765 BluetoothDevice com.example.hlm6 I get remote device name: ABCD12 2025-09-19 16:27:54.790 13765-13765 BLEManager com.example.hlm6 D 开始准备连接:ABCD12-->11:89:9A:A1:61:E8 2025-09-19 16:27:54.790 13765-13765 BluetoothDevice com.example.hlm6 I connectGatt: callingPid=13765 callingUid=10366 2025-09-19 16:27:54.790 13765-13765 BluetoothDevice com.example.hlm6 I callingApp=com.example.hlm6 2025-09-19 16:27:54.791 13765-13765 BluetoothGatt com.example.hlm6 D connect() - device: 11:89:**:**:**:E8, auto: false 2025-09-19 16:27:54.791 13765-13765 BluetoothGatt com.example.hlm6 D registerApp() 2025-09-19 16:27:54.791 13765-13765 BluetoothGatt com.example.hlm6 D registerApp() - UUID=073dcedf-8a72-4b79-a06e-0fa0542d174a 2025-09-19 16:27:54.792 13765-13765 BluetoothGatt com.example.hlm6 I connect is called 2025-09-19 16:27:54.793 13765-17170 BluetoothGatt com.example.hlm6 D onClientRegistered() - status=0 clientIf=17 2025-09-19 16:27:54.793 13765-13765 ContentValues com.example.hlm6 D btConnect: 连接蓝牙结束 2025-09-19 16:27:55.578 13765-17213 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner] start to get views' rect, type = SCENE_GESTURE_SINGLE_TAP 2025-09-19 16:27:55.579 13765-17213 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner] windowModeType: 1 2025-09-19 16:27:55.579 13765-17213 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner] displayPoint: Point(1260, 2720) 2025-09-19 16:27:55.579 13765-17213 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner] windowModeType: 1 2025-09-19 16:27:55.579 13765-17213 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner] lazyMode: 2025-09-19 16:27:55.579 13765-17213 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner] current mode is full screen 2025-09-19 16:27:55.580 13765-13765 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner] start to getViewHierarchy 2025-09-19 16:27:55.581 13765-17213 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner] deviceOrientation: 0 2025-09-19 16:27:55.581 13765-17213 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner-ScreenDirection] ROTATION_0 2025-09-19 16:27:55.581 13765-17213 HwViewRootImpl com.example.hlm6 D [DetectViewsLocationRunner] get views' rect = 13, SCENE_GESTURE_SINGLE_TAP#52,137,338,267#457,1381,799,1537#958,153,1244,309#958,360,1244,516#958,567,1244,723#958,774,1244,930#958,981,1244,1137#958,1188,1244,1344#958,1395,1244,1551#958,1602,1244,1758#958,1809,1244,1965#958,2016,1244,2172#958,2223,1244,2379 2025-09-19 16:28:02.504 13765-17162 BluetoothGatt com.example.hlm6 D onClientConnectionState() - status=133 clientIf=17 device=11:89:**:**:**:E8 2025-09-19 16:28:02.505 13765-17162 BLEManager com.example.hlm6 D onConnectionStateChange: --------链接成功回调--------- 2025-09-19 16:28:02.505 13765-17162 BLEManager com.example.hlm6 D status:133 2025-09-19 16:28:02.505 13765-17162 BLEManager com.example.hlm6 D newState:0 2025-09-19 16:28:02.507 13765-17162 BluetoothDevice com.example.hlm6 I get remote device name: ABCD12 2025-09-19 16:28:02.507 13765-17162 BLEManager com.example.hlm6 D 连接的设备:ABCD12 11:89:9A:A1:61:E8 2025-09-19 16:28:02.509 13765-17162 om.example.hlm com.example.hlm6 W Accessing hidden method Landroid/bluetooth/BluetoothGatt;->refresh()Z (unsupported, reflection, allowed) 2025-09-19 16:28:02.510 13765-17162 BluetoothGatt com.example.hlm6 D refresh() - device: 11:89:**:**:**:E8 2025-09-19 16:28:02.512 13765-17162 BLEManager com.example.hlm6 E 断开连接status:133 2025-09-19 16:28:02.513 13765-17162 BluetoothGatt com.example.hlm6 D close() 2025-09-19 16:28:02.513 13765-17162 BluetoothGatt com.example.hlm6 D unregisterApp() - mClientIf=17 2025-09-19 16:28:02.515 13765-17162 BluetoothGatt com.example.hlm6 D close() 2025-09-19 16:28:02.515 13765-17162 BluetoothGatt com.example.hlm6 D unregisterApp() - mClientIf=0 2025-09-19 16:28:02.515 13765-17162 BLEManager com.example.hlm6 E 连接失败status:133 11:89:9A:A1:61:E8
09-20
/******************************************************************************** ** Copyright (C), 2023-2123, Oplus Mobile Comm Corp., Ltd ** All rights reserved. ** ** File: - OplusTransitionController.java ** Description: ** Connector of oplus transition and shell transtion ** ** Version: 1.0 ** Date: 2023-02-02 ** Author: LiYichen@ANDROID.WMS ** TAG: OPLUS_FEATURE_SHELL_TRANSITION ** ------------------------------- Revision History: ---------------------------- ** <author> <data> <version> <desc> ** ------------------------------------------------------------------------------ ** LiYichen@ANDROID.WMS 2023-03-13 1.0 Create this module ********************************************************************************/ package com.android.wm.shell.transition; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; import android.annotation.NonNull; import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.util.ArrayMap; import android.view.SurfaceControl; import android.view.WindowManager; import android.view.animation.Animation; import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.WindowContainerTransaction; import android.window.WindowOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.flexibletask.FlexibleTaskTransitionHandler; import com.android.wm.shell.flexiblewindow.FlexibleWindowTransitionHandler; import com.android.wm.shell.putt.OnePuttTransitionHandler; import com.android.wm.shell.recents.RecentsTransitionHandler; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.shared.TransitionUtil; import com.oplus.flexiblewindow.FlexibleWindowManager; import com.oplus.flexibleactivity.FlexibleActivityTransitionHandler; import com.oplus.foldswitch.OplusFoldingSwitchAnimationManager; import com.oplus.splitscreen.SplitToggleHelper; import com.oplus.transition.FlexibleSubDisplayTransition; import com.oplus.transition.OplusInterruptTransitionManager; import com.oplus.transition.OplusShellLightOSTransition; import com.oplus.transition.OplusRemoteInterruptManager; import com.oplus.transition.OplusRotationAnimationManager; import com.oplus.transition.OplusTransitionAnimationManager; import com.oplus.transition.OplusCompatModeAnimationManager; import com.oplus.view.inputmethod.OplusInputMethodManager; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_RELAUNCH; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER; import static android.window.TransitionInfo.FLAG_FILLS_TASK; import static com.oplus.transition.FlexibleTransitionUtils.LAUNCH_SCENARIO_SUB_DISPLAY; import static com.oplus.transition.OplusTransitionAnimationManager.NO_BACKCOLOR_PACKAGE_IN_SCREEN_ROTATION; import static com.oplus.wrapper.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import java.util.ArrayList; public class OplusTransitionController { /** ** delay one second releaseSurfaces while finish-for-sync track **/ static final int DELAY_RELEASE_TIME = 1000; //#ifndef OPLUS_FEATURE_SHELL_TRANSITION //Guofu.Yang@ANDROID.WMS 2023/9/19, Add for bug 6251064,too many Offscreen Layers /** Same as OplusTransitionAnimationManager FLAG_TRANSITION_FINISH_RELEASE,no duplication with TransitionInfo#ChangeFlags **/ private static final int FLAG_TRANSITION_FINISH_RELEASE = 1 << 30; //#endif /*OPLUS_FEATURE_SHELL_TRANSITION*/ private static volatile OplusTransitionController sInstance; /* transition features */ private OplusTransitionAnimationManager mTransitionAnimation; private OplusRotationAnimationManager mRotationAnimation; private OplusShellLightOSTransition mLightOSTransition; private OplusFoldingSwitchAnimationManager mFoldSwitchAnimation; private OplusRemoteInterruptManager mRemoteInterrupt; private FlexibleSubDisplayTransition mFlexibleSubDisplayTransition; private OplusCompatModeAnimationManager mOplusCompatModeAnimationManager; private Context mContext; private Handler mHandler; private DisplayController mDisplayController; private TransactionPool mTransactionPool; private Transitions mTransitions; private boolean mLoadLightScreenRotation = false; private ShellExecutor mMainExecutor; //#ifdef OPLUS_FEATURE_FLEXIBLEWINDOW //Fuhai.Tan@ANDROID.WMS, 2023/8/4, add for ps shell transition(alm5954198) // add for ps display change private FlexibleWindowTransitionHandler mFlexibleWindowTransitionHandler; //#endif /* OPLUS_FEATURE_FLEXIBLEWINDOW */ private FlexibleTaskTransitionHandler mFlexibleTaskTransitionHandler; /* need to adapter dagger inject? */ public static OplusTransitionController getInstance() { if (sInstance == null) { synchronized (OplusTransitionController.class) { if (sInstance == null) { sInstance = new OplusTransitionController(); } } } return sInstance; } public void setTransitions(Transitions transitions) { this.mTransitions = transitions; } public void init(Context context, Handler mainHandler, DisplayController displayController, ShellExecutor mainExecutor, ShellExecutor animExecutor, TransactionPool pool) { mContext = context; mHandler = mainHandler; mDisplayController = displayController; mTransactionPool = pool; initDisplayListener(); mTransitionAnimation = new OplusTransitionAnimationManager(context); mRotationAnimation = new OplusRotationAnimationManager(context, mainExecutor, animExecutor); if (OplusShellLightOSTransition.IS_LIGHTOS) { mLightOSTransition = new OplusShellLightOSTransition(context); } mRemoteInterrupt = new OplusRemoteInterruptManager(); //#endif /* OPLUS_FEATURE_FLEXIBLEWINDOW */ //songchunlong@ANDROID.WMS, 2023/10/09, add for flexibleTask shell transition mFlexibleTaskTransitionHandler = new FlexibleTaskTransitionHandler(mainExecutor, animExecutor, pool, context, mTransitions); //#endif /* OPLUS_FEATURE_FLEXIBLETask */ //#ifdef OPLUS_FEATURE_FOLDABLE_DISPLAY_SWITCH //LiangHan@ANDROID.WMS, 2023/06/06, Add for folded switching mFoldSwitchAnimation = new OplusFoldingSwitchAnimationManager(context, mainExecutor); mFoldSwitchAnimation.registerOplusFoldSwitchStateObserver(); //#endif /* OPLUS_FEATURE_FOLDABLE_DISPLAY_SWITCH */ //#ifdef OPLUS_FEATUR_ONEPUTT //luochuang@ANDROID.WMS, 2023/07/08, Add for putt OnePuttTransitionHandler onePuttTransitionHandler = new OnePuttTransitionHandler(mainExecutor, animExecutor, pool, displayController); mTransitions.addHandler(onePuttTransitionHandler); //#endif /* OPLUS_FEATUR_ONEPUTT */ //#ifdef OPLUS_FEATURE_FLEXIBLEWINDOW //Fuhai.Tan@ANDROID.WMS, 2023/8/4, add for ps shell transition(alm5954198) if (FlexibleWindowManager.getInstance().isSupportPocketStudio(context)) { mFlexibleWindowTransitionHandler = new FlexibleWindowTransitionHandler(mainExecutor, animExecutor, pool, context, mTransitions); } //#endif /* OPLUS_FEATURE_FLEXIBLEWINDOW */ //#ifdef OPLUS_FEATURE_FLEXIBLEACTIVITY //Li.Wang@ANDROID.WMS, 2023/09/26, Add for putt FlexibleActivityTransitionHandler flexibleActivityTransitionHandler = new FlexibleActivityTransitionHandler( mainExecutor, animExecutor, pool, displayController, context); mTransitions.addHandler(flexibleActivityTransitionHandler); //#endif /* OPLUS_FEATURE_FLEXIBLEACTIVITY */ mFlexibleSubDisplayTransition = new FlexibleSubDisplayTransition(context); mOplusCompatModeAnimationManager = new OplusCompatModeAnimationManager(context); OplusInterruptTransitionManager.getInstance().init(mTransitions); } private void initDisplayListener() { mDisplayController.addDisplayWindowListener(new DisplayController.OnDisplaysChangedListener() { @Override public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { mRotationAnimation.onConfigurationChanged(newConfig); if (mLightOSTransition != null) { mLightOSTransition.onConfigurationChanged(newConfig); } if (mTransitionAnimation != null) { mTransitionAnimation.onConfigurationChanged(displayId, mDisplayController); } } }); } /** * skip default transition if need */ public boolean skipDefaultTransition(TransitionInfo info, TransitionInfo.Change change) { TransitionLog.debug("load DefaultTransition, info.getType = " + WindowManager.transitTypeToString(info.getType()) + ", info.flag = " + Integer.toHexString(info.getFlags()) + ", isTask = " + (change.getTaskInfo() != null) + "\n-- change = " + change + " " + change.getFlags() + "\n-- animationoptions = " + info.getAnimationOptions()); if (change.getLeash() == null || !change.getLeash().isValid()) { TransitionLog.w("skipDefaultTransition, leash has been released"); return true; } // #ifdef OPLUS_FEATURE_ANIMATION // Qin.Ding@ANDROID.WMS 2024/08/08, Modify for almid:7684438 if (mTransitionAnimation.skipDefaultTransitionIfNeed(info, change)) { return true; } // #endif /* OPLUS_FEATURE_ANIMATION */ //#ifdef OPLUS_FEATURE_FOLDABLE_DISPLAY_SWITCH //LiangHan@ANDROID.WMS, 2023/09/08, Add for folded switching if (mFoldSwitchAnimation.skipDefaultTransitionIfNeed(info, change)) { return true; } //#endif /* OPLUS_FEATURE_FOLDABLE_DISPLAY_SWITCH */ //#ifdef OPLUS_FEATURE_FLEXIBLEWINDOW //Fuhai.Tan@ANDROID.WMS, 2023/8/4, add for ps shell transition(alm5954198) // add for ps display change if (mFlexibleWindowTransitionHandler != null) { return mFlexibleWindowTransitionHandler.skipDefaultTransition(info, change); } //#endif /* OPLUS_FEATURE_FLEXIBLEWINDOW */ return false; } /** * hook for default activity/task transition */ public Animation hookTransitionAnimation(TransitionInfo info, @NonNull TransitionInfo.Change change, boolean translucent, int wallpaperTransit, int animArrt) { //#ifdef OPLUS_FEATURE_SHELL_TRANSITION //WeiJianjun@ANDROID.WMS, 2024/01/30, Add for feature 6789761 boolean isTask = change.getTaskInfo() != null; final int changeMode = change.getMode(); final boolean enter = TransitionUtil.isOpeningType(changeMode); //#endif /*OPLUS_FEATURE_SHELL_TRANSITION*/ //#ifdef OPLUS_FEATURE_SHELL_TRANSITION //JiangYi@ANDROID.WMS 2023/10/20, Add for bug:6134388 Animation a = null; if (mTransitionAnimation.isWsnSwitch(info.getType(), change) && !mTransitionAnimation.isLargeScreenTaskSwitch(info.getType(), change)) { a = mTransitionAnimation.loadWsnTaskSwitchAnimation(info.getType(), enter); if (a != null) { return a; } } //#endif /* OPLUS_FEATURE_SHELL_TRANSITIONH */ //#ifndef OPLUS_FEATURE_LARGESCREEN_TASK_SWITCH //Changzhi.Wu@ANDROID.WMS, 2023/9/4 : Add for task switch in large screen Animation animation = null; if (mTransitionAnimation.isLargeScreenTaskSwitch(info.getType(), change)) { animation = mTransitionAnimation.loadLargeScreenTaskSwitchAnimation(info.getType(), enter); if (animation != null) { return animation; } } ////#endif /* OPLUS_FEATURE_LARGESCREEN_TASK_SWITCH */ if (mLightOSTransition != null) { Animation lightOSTransition = mLightOSTransition.loadLightCompactWindowAnimation(info.getAnimationOptions(), enter); if (lightOSTransition != null) { return lightOSTransition; } } if (mOplusCompatModeAnimationManager != null) { Animation oplusCompatModeAnimation = mOplusCompatModeAnimationManager .getOplusCompatModeAnimation(info, enter, isTask, translucent, wallpaperTransit); if (oplusCompatModeAnimation != null) { return oplusCompatModeAnimation; } } return mTransitionAnimation.getOplusDefaultTransition(info, change, enter, translucent, wallpaperTransit, animArrt); } /** * hook for default custom transition */ public Animation hookCustomAnimation(TransitionInfo.AnimationOptions options, int transit, boolean enter) { return mTransitionAnimation.checkAndLoadCustomAnimation(options, transit, enter); } /** * adjust layer * * @param position * @param info * @param transaction */ public void adjustZBoostForLayer(int index, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction transaction) { mTransitionAnimation.adjustZBoostForLayer(index, info, transaction); } /** * hook for screen rotation animation */ public Animation[] hookRotationAnimation(int delta, int originWidth, int originHeight, int finalWidth, int finalHeight, SurfaceControl surface, TransitionInfo info) { //#ifdef OPLUS_FEATURE_SHELL_TRANSITION //Nana.Pang@ANDROID.WMS,2024/02/23, add for dark mode transition Animation[] oplusAnimation = null; // LightOS rotation animation if (loadLightScreenRotationAnimations()) { oplusAnimation = mRotationAnimation.getLightScreenRotationAnimations(delta, originWidth, originHeight, finalWidth, finalHeight); mLoadLightScreenRotation = false; } else { // normal rotation animation oplusAnimation = mRotationAnimation.getDefaultRotationAnimations(delta, originWidth, originHeight, finalWidth, finalHeight); } //#endif /*OPLUS_FEATURE_SHELL_TRANSITION*/ // device fold rotation animation if (TransitionAnimationUtil.isFoldDevice() && !TransitionAnimationUtil.isFlipDevice() && isFoldChangeOpen(info)) { oplusAnimation = mFoldSwitchAnimation.getDeviceFoldingRotationAnimations(delta, originWidth, originHeight, finalWidth, finalHeight); } // BracketModeAnimations return oplusAnimation; } /** * hook for screen rotation back color. */ public boolean hookScreenRotateBackColor(TransitionInfo info, TransitionInfo.Change change) { if ((TransitionAnimationUtil.isFoldDevice() && !TransitionAnimationUtil.isFlipDevice()) || TransitionAnimationUtil.isTabletDevice()) { return !(change.hasFlags(TransitionInfo.FLAG_SHOW_WALLPAPER) && mRotationAnimation.isLauncherRotateScene(info)); } return true; } public boolean hookScreenRotateBackColorAnimation(float[] backColorRGBFloats, Animation animation, SurfaceControl leash, TransactionPool pool) { return mRotationAnimation.startScreenRotateBackColorAnimation(backColorRGBFloats, animation, leash, pool); } public SurfaceControl.Transaction hookTransitionFinish(TransitionInfo activeInfo, SurfaceControl.Transaction fullFinish, TransitionInfo readyInfo, WindowContainerTransaction wct, WindowOrganizer windowOrganizer) { SurfaceControl.Transaction result = null; //#ifdef OPLUS_FEATURE_SPLITSCREEN //Chenzhe@ANDROID.WMS, 2023/06/23, add for android U shell_transition if (windowOrganizer != null && ((ShellTaskOrganizer) windowOrganizer).getStageCoordinator() != null) { ((ShellTaskOrganizer) windowOrganizer).getStageCoordinator().hookFinishTransactionOnFinish(activeInfo, fullFinish); } //#endif /* OPLUS_FEATURE_SPLITSCREEN */ result = mRemoteInterrupt.fixFinishTransaction(activeInfo, fullFinish, readyInfo, wct); return (result != null) ? result : fullFinish; } public void hookRecentStart(TransitionInfo info, ArrayMap<SurfaceControl, SurfaceControl> leashMap, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT) { boolean isFinishHideIme = false; for (int i = 0; i < info.getChanges().size(); ++i) { final TransitionInfo.Change change = info.getChanges().get(i); mRemoteInterrupt.fixAlphaInRecentStart(change, leashMap, startT); if (!isFinishHideIme && change.getTaskInfo() != null && TransitionAnimationUtil.getScenario(change.getTaskInfo().getConfiguration()) == LAUNCH_SCENARIO_SUB_DISPLAY) { OplusInputMethodManager.getInstance().hideCurrentInputMethod(); isFinishHideIme = true; } } if (mOplusCompatModeAnimationManager != null) { mOplusCompatModeAnimationManager.onRecentAnimationStart(); } //#ifdef OPLUS_FEATURE_INTERRUPT_TRANSITION //LiYichen@ANDROID.WMS 2023//, Add for oplus shell transition OplusInterruptTransitionManager.getInstance().resetRemoteInterrupt(); //#endif /*OPLUS_FEATURE_INTERRUPT_TRANSITION*/ } public void hookRecentFinish() { if (mOplusCompatModeAnimationManager != null) { mOplusCompatModeAnimationManager.onRecentAnimationEnd(); } } public void hookProcessReady() { mRemoteInterrupt.reset(); } /** * add for global rounded corner */ public float getRoundedCornersIfNeed(int transit, int wallpaperTransit, boolean enter, Animation animation, TransitionInfo.AnimationOptions options) { return mTransitionAnimation.getRoundedCornersIfNeed(transit, wallpaperTransit, enter, animation, options); } public boolean isRemoteInterrupt() { return mRemoteInterrupt != null && mRemoteInterrupt.isRemoteInterrupt(); } public boolean skipFinishForSync(Transitions.TransitionHandler activeHandler, TransitionInfo readyInfo) { // L-A-L-B , B can start if (readyInfo.getTrack() == OplusInterruptTransitionManager.INTERRUPT_TRACK) { TransitionLog.always("skipFinishForSync when interrupt track"); return true; } // bugid: 6323681/6703089/6989073 if (activeHandler != null && activeHandler instanceof RecentsTransitionHandler && (readyInfo.getType() == TRANSIT_TO_FRONT || readyInfo.getType() == TRANSIT_OPEN)) { TransitionLog.w("skipFinishForSync readyInfo.getType:" + readyInfo.getType()); return true; } return false; } /** * get extra bundle params for animation */ public Bundle getAnimExtraBundle(TransitionInfo info, TransitionInfo.Change change) { //#ifdef OPLUS_FEATURE_ADFR_V32_SUPPORT //chendongqi@oppo.com, 2024/08/10, change parameter for setFrameRate return (mFlexibleSubDisplayTransition != null) ? mFlexibleSubDisplayTransition.getAnimExtraBundle(change) : null; //#endif /* OPLUS_FEATURE_ADFR_V32_SUPPORT */ } public boolean canCustomizeAppTransition(TransitionInfo.AnimationOptions options, int type, TransitionInfo.Change change) { boolean isTranslucentCloseTransition = (type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK) && (change.getFlags() & FLAG_TRANSLUCENT) != 0; return options.getOverrideTaskTransition() || TransitionAnimationUtil.isSystemApp(options.getPackageName()) || isTranslucentCloseTransition; } public void adjustDefaultTransitionStartState(int index, Animation a, TransitionInfo.Change change, TransitionInfo info, SurfaceControl.Transaction startTransaction) { mTransitionAnimation.adjustAnimHierarchyLayer(index, a, change, info, startTransaction); mTransitionAnimation.adjustAnimStartAlpha(info, change, startTransaction); } public void setAlphaForCustom(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change, SurfaceControl.Transaction startT, Animation a) { mTransitionAnimation.setAlphaForCustom(info, change, startT, a); } // #ifdef OPLUS_FEATURE_ANIMATION // Qin.Ding@ANDROID.WMS 2023/10/14, Modify for almid:6271944 public boolean matchRemoteFilter(@NonNull TransitionInfo info) { return mTransitionAnimation.matchRemoteFilter(info); } // Qin.Ding@ANDROID.WMS 2024/05/23, add for almid:6990651 /** * isWallpaperTransitionIntraClose * * @param info TransitionInfo * @return True if hasOpenWallpaper and hasCloseWallpaper and TRANSIT_CLOSE. */ public boolean isWallpaperTransitionIntraClose(@NonNull TransitionInfo info) { return mTransitionAnimation.isWallpaperTransitionIntraClose(info); } // #endif /* OPLUS_FEATURE_ANIMATION */ // #ifdef OPLUS_FEATURE_ANIMATION // zhengdongtao@ANDROID.WMS 2024/05/28, Modify for almid:6271944 public boolean matchCustomFilter(@NonNull TransitionInfo info) { return mTransitionAnimation.isCustomFilter(info); } public void adjustWsnHierarchyLayer(TransitionInfo info, SurfaceControl.Transaction startTransaction) { mTransitionAnimation.adjustWsnHierarchyLayer(info, startTransaction); } // #endif /* OPLUS_FEATURE_ANIMATION */ // #ifdef OPLUS_FEATURE_ANIMATION // Qin.Ding@ANDROID.WMS 2023/10/17, Modify for almid:6347638 public void registerReceiverForPkgRemoved() { mTransitionAnimation.registerReceiverForPkgRemoved(); } // #endif /* OPLUS_FEATURE_ANIMATION */ // #ifdef OPLUS_FEATURE_REMOTE_INTERRUPT_ANIMATION // Qin.Ding@ANDROID.WMS 2023/11/20, Modify for almid:6448325 public boolean ignoreAbort(@NonNull TransitionInfo info) { return mTransitionAnimation.ignoreAbort(info); } // #endif /* OPLUS_FEATURE_REMOTE_INTERRUPT_ANIMATION */ public Bundle getTaskAnimExtraBundle(TransitionInfo info) { return mFlexibleSubDisplayTransition.getTaskAnimExtraBundle(info); } public boolean disableTaskBackgroundColor() { TransitionLog.debug("disable task background color"); return true; } public boolean shouldHideSurface(@NonNull TransitionInfo.Change change) { return mTransitionAnimation.shouldHideSurface(change); } public boolean loadLightScreenRotationAnimations() { return OplusShellLightOSTransition.IS_LIGHTOS || mLoadLightScreenRotation; } public void loadLightScreenRotationForApp(@NonNull TransitionInfo info) { for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); boolean isChangeType = (change.getMode() == TRANSIT_CHANGE) && (info.getType() == TRANSIT_CHANGE); boolean rotationChange = change.getStartRotation() != change.getEndRotation(); boolean lightOSAppType = isLightOSAppType(change); if (isChangeType && rotationChange && lightOSAppType) { mLoadLightScreenRotation = true; return; } } } public boolean isLightOSAppType(@NonNull TransitionInfo.Change change) { ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); ComponentName realActivity = (taskInfo != null) ? taskInfo.realActivity : null; if (realActivity != null) { String targetPackage = realActivity.getPackageName(); for (String packageName : NO_BACKCOLOR_PACKAGE_IN_SCREEN_ROTATION) { if (packageName.equals(targetPackage)) { return true; } } } return false; } //#ifdef OPLUS_FEATURE_SHELL_TRANSITION //WeiJianjun@ANDROID.WMS, 2024/01/30, Add for feature 6789761 public boolean shouldLoadCustomAnimation(TransitionInfo.AnimationOptions options, int animAttr) { return TransitionAnimationUtil.shouldLoadCustomAnimation(options, animAttr); } //#endif /*OPLUS_FEATURE_SHELL_TRANSITION*/ //#ifdef OPLUS_FEATURE_FLEXIBLEWINDOW //#LiuXuefeng@ANDROID.WMS, 2023/10/08, Add for 6280618 public void hookTransitionFinishForFlexibleMinimize(Transitions.ActiveTransition active, SurfaceControl.Transaction fullFinish) { ArrayList<TransitionInfo> animatingInfo = new ArrayList<>(); TransitionInfo info = active.mInfo; if (info != null) { animatingInfo.add(info); } if (active.mMerged != null) { for (Transitions.ActiveTransition at : active.mMerged) { if (at != null && at.mInfo != null && (at.mInfo.getType() == TRANSIT_OPEN || at.mInfo.getType() == TRANSIT_TO_FRONT)) { animatingInfo.add(at.mInfo); } } } SplitScreenController controller = SplitToggleHelper.getInstance().peekDivider(); if (controller != null && !animatingInfo.isEmpty()) { controller.adjustChangeForFlexibleMinimizeIfNeed(active.mHandler, animatingInfo, fullFinish); } } //#endif /* OPLUS_FEATURE_FLEXIBLEWINDOW */ public boolean disableForceSyncIfNeed(int tolerance, IBinder reasonTransition, IBinder playingTransition, Transitions.TransitionHandler reasonHandler, Transitions.TransitionHandler playingHandler, TransitionInfo reasonInfo, TransitionInfo playingInfo) { SplitScreenController controller = SplitToggleHelper.getInstance().peekDivider(); if (controller != null && controller.getExtImpl() != null) { return controller.getExtImpl().disableForceSyncIfNeed(tolerance, reasonTransition, playingTransition, reasonHandler, playingHandler, reasonInfo, playingInfo); } return false; } public boolean abortTransitionIfNeeded(Transitions.ActiveTransition ready, Transitions.ActiveTransition playing) { if (ready.mInfo.getType() == TRANSIT_RELAUNCH && playing != null && playing.mHandler instanceof RecentsTransitionHandler) { TransitionLog.always("Abort transition " + ready + " when recent " + playing + " is playing"); return true; } if (ready.mInfo.getType() == TRANSIT_OPEN) { final int changeSize = ready.mInfo.getChanges().size(); for (int i = changeSize - 1; i >= 0; --i) { final TransitionInfo.Change change = ready.mInfo.getChanges().get(i); if (change.hasAllFlags(FLAG_TRANSLUCENT | FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY | FLAG_FILLS_TASK | FLAG_IS_BEHIND_STARTING_WINDOW)) { TransitionLog.always("Abort transition " + ready + " when translucent embed activities transit."); return true; } } } return false; } //#ifndef OPLUS_FEATURE_SHELL_TRANSITION //Guofu.Yang@ANDROID.WMS 2023/9/19, Add for bug 6251064,too many Offscreen Layers bugid:7240816 public void releaseSurfaces(TransitionInfo info) { if (info == null) { return; } for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.hasFlags(FLAG_TRANSITION_FINISH_RELEASE)) { if ((change.getLeash() != null) && change.getLeash().isValid() && (info.getType() < TRANSIT_FIRST_CUSTOM) && ((change.getTaskInfo() == null) || (change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN))) { change.getLeash().release(); } } } } //#endif /*OPLUS_FEATURE_SHELL_TRANSITION*/ public boolean hasSameUserId(RemoteTransition pendingRemote, TransitionInfo info) { return mTransitionAnimation.hasSameUserId(pendingRemote, info); } public void markUserIdTransition(@NonNull RemoteTransition remoteTransition) { mTransitionAnimation.markUserIdTransition(remoteTransition); } public boolean hookStartRecentsTransiton(RecentsTransitionHandler.RecentsController controller, WindowContainerTransaction wct) { if (mTransitionAnimation.cancelStartRecentsTransition(controller, mTransitions)) { return false; } //#ifdef OPLUS_FEATURE_SHELL_TRANSITION //Li.Wang@ANDROID.WMS, 2023/09/23, Add for oplus shell transition(aimid 6254609) OplusRemoteInterruptManager.ReflectionHelper.setRecentTransition(wct, true); //#endif /*OPLUS_FEATURE_SHELL_TRANSITION*/ return true; } public boolean isDeviceFolded() { return mFoldSwitchAnimation.getDeviceFolded(); } public boolean ifMergeIntoRemote(TransitionInfo info, SurfaceControl.Transaction t, Transitions.TransitionFinishCallback finishCallback, boolean mergedIsNotRemote) { return !OplusInterruptTransitionManager.getInstance().ifMergeIntoRemote(info, t, finishCallback, mergedIsNotRemote) && !mTransitionAnimation.shouldMergeToRemote(info, mTransitions); } public void initAnimLayerWhenFolding(SurfaceControl.Transaction t, SurfaceControl animLeash, TransitionInfo info, int startWidth, int startHeight) { mFoldSwitchAnimation.initAnimLayerWhenFolding(t, animLeash, info, startWidth, startHeight); } public boolean cancelAnimationIfNeedWhenFolding(TransitionInfo info) { return mFoldSwitchAnimation.cancelAnimationIfNeedWhenFolding(info); } public boolean isFoldChangeOpen(TransitionInfo info) { return mFoldSwitchAnimation.getFoldChangeType(info) == OplusFoldingSwitchAnimationManager.FOLD_SWITCHING_TRANSITION_OPEN; } } 解析以上该类和该类的所有方法的作用和含义
07-18
内容概要:本文介绍了一个基于MATLAB实现的无人机三维路径规划项目,采用蚁群算法(ACO)与多层感知机(MLP)相结合的混合模型(ACO-MLP)。该模型通过三维环境离散化建模,利用ACO进行全局路径搜索,并引入MLP对环境特征进行自适应学习与启发因子优化,实现路径的动态调整与多目标优化。项目解决了高维空间建模、动态障碍规避、局部最优陷阱、算法实时性及多目标权衡等关键技术难题,结合并行计算与参数自适应机制,提升了路径规划的智能性、安全性和工程适用性。文中提供了详细的模型架构、核心算法流程及MATLAB代码示例,涵盖空间建模、信息素更新、MLP训练与融合优化等关键步骤。; 适合人群:具备一定MATLAB编程基础,熟悉智能优化算法与神经网络的高校学生、科研人员及从事无人机路径规划相关工作的工程师;适合从事智能无人系统、自动驾驶、机器人导航等领域的研究人员; 使用场景及目标:①应用于复杂三维环境下的无人机路径规划,如城市物流、灾害救援、军事侦察等场景;②实现飞行安全、能耗优化、路径平滑与实时避障等多目标协同优化;③为智能无人系统的自主决策与环境适应能力提供算法支持; 阅读建议:此资源结合理论模型与MATLAB实践,建议读者在理解ACO与MLP基本原理的基础上,结合代码示例进行仿真调试,重点关注ACO-MLP融合机制、多目标优化函数设计及参数自适应策略的实现,以深入掌握混合智能算法在工程中的应用方法。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2021、小林子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值