自动化零件服务商 - 供应SMC,FESTO,CKD全新正品气动元件
自动化零件服务商 - 供应SMC,FESTO,CKD全新正品气动元件
自动化零件服务商 - 供应SMC,FESTO,CKD全新正品气动元件

前言

本文监听MediaButton是如何分发,以及分发到对应的监听者中。

本想改个名字,后面想都跟MediaSession有关系就延用。

个人流水账,随便跟一下。

正文

接之前《简单跟一下MediaSession源码1》,我们设置过MediaSession.Callback的监听,今天我们介绍按键上下曲的监听。

  1. //MediaSession.Callback的实现
  2. private final MediaSession.Callback mediaSessionCallback = new MediaSession.Callback() {
  3.   @Override
  4.   public boolean onMediaButtonEvent(@NonNull Intent intent) {
  5.       String action = intent.getAction();
  6.       //判断是否有焦点
  7.       if (action.equals(Intent.ACTION_MEDIA_BUTTON) && !isAudioFocusLoss()) {
  8.       //获取keyevent事件
  9.           KeyEvent keyevent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
  10.           int keyCode = keyevent.getKeyCode();
  11.           int keyAction = keyevent.getAction();
  12.           //弹起响应
  13.           if (keyAction == KeyEvent.ACTION_UP) {
  14.               switch (keyCode) {
  15.                   case KeyEvent.KEYCODE_MEDIA_NEXT:
  16.                   //下一曲
  17.                       onSkipToNext();
  18.                       return true;
  19.                   case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
  20.                   //上一曲
  21.                       onSkipToPrevious();
  22.                       return true;
  23.                   case KeyEvent.KEYCODE_MEDIA_PAUSE:
  24.                   //暂停
  25.                       return true;
  26.                   case KeyEvent.KEYCODE_MEDIA_PLAY:
  27.                   //播放
  28.                       return true;
  29.               }
  30.           }
  31.       }
  32.       return super.onMediaButtonEvent(intent);
  33.   }
  34. };
复制

这里我们跟一下上一曲KeyEvent.KEYCODE_MEDIA_PREVIOUS,键值为88。

之前分析说过。

  1. MediaSession mMediaSession = new MediaSession(MusicApp.getContext(), TAG);
  2. //设置MediaSession.Callback监听
  3. mMediaSession.setCallback(mediaSessionCallback);
复制

MediaSession.java

  1. frameworks\base\media\java\android\media\session\MediaSession.java
复制
setCallback()
  1. public void setCallback(@Nullable Callback callback) {
  2.   setCallback(callback, null);
  3. }
  4. public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
  5.   synchronized (mLock) {
  6.       if (mCallback != null) {
  7.           mCallback.mCallback.mSession = null;
  8.           mCallback.removeCallbacksAndMessages(null);
  9.       }
  10.       if (callback == null) {
  11.           mCallback = null;
  12.           return;
  13.       }
  14.       if (handler == null) {
  15.           handler = new Handler();
  16.       }
  17.       callback.mSession = this;
  18.       CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),callback);
  19.       mCallback = msgHandler;
  20.   }
  21. }
复制

注意这里的mCallback是CallbackMessageHandler(重写的Handler)对象。同时把callback (MediaSession.Callback对象)传入到mCallback中。

既然传入到Handler中,那自然看handleMessage中的消息处理。

CallbackMessageHandler.handleMessage()
  1. @Override
  2. public void handleMessage(Message msg) {
  3. //略
  4.   switch (msg.what) {
  5.       case MSG_COMMAND:
  6.           Command cmd = (Command) obj;
  7.           mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
  8.           break;
  9.       case MSG_MEDIA_BUTTON:
  10.           mCallback.onMediaButtonEvent((Intent) obj);
  11.           break;
  12. //略
  13. }
  14. }
复制

找到MSG_MEDIA_BUTTON,也就是这里回调了mCallback。

注意这里的mCallback是CallbackMessageHandler中的遍历,也就是上面传入的MediaSession.Callback。

既然知道发送的MSG_MEDIA_BUTTON消息,那就是找到发送的地方即可。

dispatchMediaButton()
  1. void dispatchMediaButton(RemoteUserInfo caller, Intent mediaButtonIntent) {
  2.   postToCallback(caller, CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent, null);
  3. }
复制

经过跟踪,定位到CallbackStub.onMediaButton()中。

MediaSession.CallbackStub

在看CallbackStub.onMediaButton()之前,先看一下CallbackStub类。

CallbackStub比较重要,很多方法都是这里回调的。

  1. public static class CallbackStub extends ISessionCallback.Stub {
  2. private WeakReference<MediaSession> mMediaSession;
  3.   public CallbackStub(MediaSession session) {
  4.       mMediaSession = new WeakReference<>(session);
  5.   }
  6. //略
  7. }
复制

记住这里传入的mMediaSession。

CallbackStub.onMediaButton()
  1. @Override
  2. public void onMediaButton(String packageName, int pid, int uid, Intent mediaButtonIntent,
  3.       int sequenceNumber, ResultReceiver cb) {
  4.   //获取构造函数中传入的MediaSession
  5.   MediaSession session = mMediaSession.get();
  6.   try {
  7.       if (session != null) {
  8.       //分发Media Button
  9.           session.dispatchMediaButton(createRemoteUserInfo(packageName, pid, uid),
  10.                   mediaButtonIntent);
  11.       }
  12.   } finally {
  13.       if (cb != null) {
  14.           cb.send(sequenceNumber, null);
  15.       }
  16.   }
  17. }
复制

看何时创建CallbackStub,也就是mMediaSession的初始化。

CallbackStub初始化是在MediaSession构造函数中。

MediaSession()
  1. public MediaSession(@NonNull Context context, @NonNull String tag,
  2.       @Nullable Bundle sessionInfo) {
  3. //略
  4. //创建CallbackStub对象
  5.   mCbStub = new CallbackStub(this);
  6. //创建MediaSessionManager对象
  7.   MediaSessionManager manager = (MediaSessionManager) context
  8.           .getSystemService(Context.MEDIA_SESSION_SERVICE);
  9.   try {
  10. //这里传入了mCbStub
  11.       mBinder = manager.createSession(mCbStub, tag, sessionInfo);
  12.       mSessionToken = new Token(Process.myUid(), mBinder.getController());
  13. //创建MediaController对象
  14.       mController = new MediaController(context, mSessionToken);
  15.   } catch (RemoteException e) {
  16.       throw new RuntimeException("Remote error creating session.", e);
  17.   }
  18. }
复制

也就是传入CallbackStub的当前MediaSession对象,也就是上面的mMediaSession。

在创建mBinder时,传入的第一个参数为mCbStub(CallbackStub对象),跟上,这个是重点。

  1. mBinder = manager.createSession(mCbStub, tag, sessionInfo);
复制

MediaSessionManager.java

先看MediaSessionManager中的createSession()

createSession()
  1. @NonNull
  2. public ISession createSession(@NonNull MediaSession.CallbackStub cbStub, @NonNull String tag,
  3.       @Nullable Bundle sessionInfo) {
  4. //略
  5.   try {
  6.       return mService.createSession(mContext.getPackageName(), cbStub, tag, sessionInfo,
  7.               UserHandle.myUserId());
  8.   } catch (RemoteException e) {
  9.       throw new RuntimeException(e);
  10.   }
  11. }
复制

之前介绍过,mService是SessionManagerImpl在MediaSessionManager的代理对象。

SessionManagerImpl在MediaSessionService.java中,其实就是MediaSessionService跟MediaSessionManager沟通的桥梁。

MediaSessionService.java

  1. frameworks\base\services\core\java\com\android\server\media\MediaSessionService.java
复制

我们看SessionManagerImpl中的createSession()

SessionManagerImpl.createSession()

紧跟上面,传入的mCbStub是第二个参数,也就是ISessionCallback。

我们上面也说了CallbackStub是实现于ISessionCallback。

  1. @Override
  2. public ISession createSession(String packageName, ISessionCallback cb, String tag,
  3.       Bundle sessionInfo, int userId) throws RemoteException {
  4.   final int pid = Binder.getCallingPid();
  5.   final int uid = Binder.getCallingUid();
  6.   final long token = Binder.clearCallingIdentity();
  7.   try {
  8.       enforcePackageName(packageName, uid);
  9.       int resolvedUserId = handleIncomingUser(pid, uid, userId, packageName);
  10.       if (cb == null) {
  11.           throw new IllegalArgumentException("Controller callback cannot be null");
  12.       }
  13. //创建MediaSessionRecordImpl
  14. //第5个参数为cb
  15.       MediaSessionRecord session = createSessionInternal(
  16.               pid, uid, resolvedUserId, packageName, cb, tag, sessionInfo);
  17.       if (session == null) {
  18.           throw new IllegalStateException("Failed to create a new session record");
  19.       }
  20.       ISession sessionBinder = session.getSessionBinder();
  21.       if (sessionBinder == null) {
  22.           throw new IllegalStateException("Invalid session record");
  23.       }
  24.       return sessionBinder;
  25.   } catch (Exception e) {
  26.       throw e;
  27.   } finally {
  28.       Binder.restoreCallingIdentity(token);
  29.   }
  30. }
复制

上面通过createSessionInternal创建了MediaSessionRecord,然后传回了MediaSessionRecord的Binder。

重点关注createSessionInternal(),注意,这里也传入了cb。

createSessionInternal()
  1. private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
  2.       String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
  3.   synchronized (mLock) {
  4.       int policies = 0;
  5.       if (mCustomMediaSessionPolicyProvider != null) {
  6.           policies = mCustomMediaSessionPolicyProvider.getSessionPoliciesForApplication(
  7.                   callerUid, callerPackageName);
  8.       }
  9.       FullUserRecord user = getFullUserRecordLocked(userId);
  10.       if (user == null) {
  11.           throw new RuntimeException("Session request from invalid user.");
  12.       }
  13.       final int sessionCount = user.mUidToSessionCount.get(callerUid, 0);
  14.       if (sessionCount >= SESSION_CREATION_LIMIT_PER_UID
  15.               && !hasMediaControlPermission(callerPid, callerUid)) {
  16.           throw new RuntimeException("Created too many sessions. count="
  17.                   + sessionCount + ")");
  18.       }
  19.       final MediaSessionRecord session;
  20.       try {
  21.       //cb传入了MediaSessionRecord
  22.           session = new MediaSessionRecord(callerPid, callerUid, userId,
  23.                   callerPackageName, cb, tag, sessionInfo, this,
  24.                   mRecordThread.getLooper(), policies);
  25.       } catch (RemoteException e) {
  26.           throw new RuntimeException("Media Session owner died prematurely.", e);
  27.       }
  28.       user.mUidToSessionCount.put(callerUid, sessionCount + 1);
  29.       user.mPriorityStack.addSession(session);
  30.       mHandler.postSessionsChanged(session);
  31.       return session;
  32.   }
  33. }
复制

忽略其他的,暂时关注ISessionCallback对象,传入到MediaSessionRecord中。

MediaSessionRecord.java

MediaSessionRecord()
  1. public MediaSessionRecord(int ownerPid, int ownerUid, int userId, String ownerPackageName,
  2.       ISessionCallback cb, String tag, Bundle sessionInfo,
  3.       MediaSessionService service, Looper handlerLooper, int policies)
  4.       throws RemoteException {
  5. //略
  6. //ISessionCallback传入到SessionCb
  7.   mSessionCb = new SessionCb(cb);
  8. //略
  9. }
复制

SessionCb定义在MediaSessionRecord()中。

SessionCb()
  1. class SessionCb {
  2.   private final ISessionCallback mCb;
  3.   SessionCb(ISessionCallback cb) {
  4.       mCb = cb;
  5.   }
  6.   public boolean sendMediaButton(String packageName, int pid, int uid,
  7.               boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
  8.     //略
  9.     }
  10.     public void play(String packageName, int pid, int uid) {
  11.   //略
  12.   }
  13.   //略
  14. }
复制

SessionCb中很多方法,都是跟ISessionCallback接口中定义的方法一样。

CallBackStub的的回调是在这里的。当前我们这里重点关注sendMediaButton()。

这里定义了两个sendMediaButton(),经过但两个最终回调的方法不一样。

看第一个即可。

sendMediaButton()
  1. public boolean sendMediaButton(String packageName, int pid, int uid,
  2.       boolean asSystemService, KeyEvent keyEvent, int sequenceId, ResultReceiver cb) {
  3.   try {
  4.       if (KeyEvent.isMediaSessionKey(keyEvent.getKeyCode())) {
  5.           final String reason = "action=" + KeyEvent.actionToString(keyEvent.getAction())
  6.                   + ";code=" + KeyEvent.keyCodeToString(keyEvent.getKeyCode());
  7.           mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(),
  8.                   pid, uid, packageName, reason);
  9.       }
  10. //asSystemService为true,传入的就是true
  11.       if (asSystemService) {
  12. //走这里
  13.           mCb.onMediaButton(mContext.getPackageName(), Process.myPid(),
  14.                   Process.SYSTEM_UID, createMediaButtonIntent(keyEvent), sequenceId, cb);
  15.       } else {
  16.           mCb.onMediaButton(packageName, pid, uid,
  17.                   createMediaButtonIntent(keyEvent), sequenceId, cb);
  18.       }
  19.       return true;
  20.   } catch (RemoteException e) {
  21.   }
  22.   return false;
  23. }
复制

MediaSessionService.java

最后发现sendMediaButton()是在MediaSessionService中的SessionManagerImpl.dispatchMediaKeyEventLocked()调用的。

SessionManagerImpl.dispatchMediaKeyEventLocked()
  1. private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
  2.       boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
  3. //略
  4. MediaSessionRecord session = null;
  5. MediaButtonReceiverHolder mediaButtonReceiverHolder = null;
  6. //略
  7. //上面创建的临时对象,都为null
  8.   if (session == null && mediaButtonReceiverHolder == null) {
  9. //获取当前MediaSessionRecord
  10. //这里重点关注
  11.       session = (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked();
  12. //已经不为null了
  13.       if (session == null) {
  14.           mediaButtonReceiverHolder =
  15.                   mCurrentFullUserRecord.mLastMediaButtonReceiverHolder;
  16.       }
  17.   }
  18. //不为null咯
  19.   if (session != null) {
  20. //为true
  21.       if (needWakeLock) {
  22.           mKeyEventReceiver.acquireWakeLockLocked();
  23.       }
  24. //调用sendMediaButton发送,这就是调用上面的了
  25.       session.sendMediaButton(packageName, pid, uid, asSystemService, keyEvent,
  26.               needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
  27.               mKeyEventReceiver);
  28.   } else if (mediaButtonReceiverHolder != null) {
  29. //略
  30.   }
  31. }
复制

调用分发的已经知道,但到底分发给谁,就需要看获取MediaSessionRecord的方法。

  1. session = (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked();
复制

mCurrentFullUserRecord是FullUserRecord对象。

FullUserRecord.getMediaButtonSessionLocked()
  1. private MediaSessionRecordImpl getMediaButtonSessionLocked() {
  2. boolean gloobal = isGlobalPriorityActiveLocked();
  3.   return isGlobalPriorityActiveLocked()
  4.           ? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
  5. }
复制

这里要判断是否有全局Session,这个优先级高于其他的。

isGlobalPriorityActiveLocked()判断之前有介绍过,当前没有注册全局MediaSession,跳过。

因此返回的是

  1. mPriorityStack.getMediaButtonSession()
复制

mPriorityStack是MediaSessionStack对象。

MediaSessionStack.java

  1. frameworks\base\services\core\java\com\android\server\media\MediaSessionStack.java
复制
getMediaButtonSession()
  1. public MediaSessionRecordImpl getMediaButtonSession() {
  2.   return mMediaButtonSession;
  3. }
复制

这里返回的mMediaButtonSession是当前能收到Media Button的Session。

所以之前分析最新的mMediaButtonSession就是可以接收到Media Button的回应。

MediaSessionService.java

继续回到上面dispatchMediaKeyEventLocked(),看一下哪里分发按键的。

dispatchMediaKeyEventLocked()是在KeyEventHandler.handleKeyEventLocked()中调用的。

跟上。

KeyEventHandler.handleKeyEventLocked()
  1. void handleKeyEventLocked(String packageName, int pid, int uid,
  2.       boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock,
  3.       String opPackageName, int stream, boolean musicOnly) {
  4.   if (keyEvent.isCanceled()) {
  5.       return;
  6.   }
  7.   int overriddenKeyEvents = 0;
  8.   if (mCustomMediaKeyDispatcher != null
  9.           && mCustomMediaKeyDispatcher.getOverriddenKeyEvents() != null) {
  10.       overriddenKeyEvents = mCustomMediaKeyDispatcher.getOverriddenKeyEvents()
  11.               .get(keyEvent.getKeyCode());
  12.   }
  13.   cancelTrackingIfNeeded(packageName, pid, uid, asSystemService, keyEvent,
  14.           needWakeLock, opPackageName, stream, musicOnly, overriddenKeyEvents);
  15. //判断是否需要Tracking,
  16.   if (!needTracking(keyEvent, overriddenKeyEvents)) {
  17. //我们暂时只分析down事件,因此进入这里
  18. //不是音量类型
  19.       if (mKeyType == KEY_TYPE_VOLUME) {
  20.           dispatchVolumeKeyEventLocked(packageName, opPackageName, pid, uid,
  21.                   asSystemService, keyEvent, stream, musicOnly);
  22.       } else {
  23. //走这里
  24.           dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
  25.                   keyEvent, needWakeLock);
  26.       }
  27.       return;
  28.   }
  29. //下面的暂时不看,略
  30. }
复制

当前我这只分析down事件,up事件一样流程。

KeyEventHandler.handleMediaKeyEventLocked()

这里调用了上面的handleKeyEventLocked()

  1. void handleMediaKeyEventLocked(String packageName, int pid, int uid,
  2.         boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
  3.     handleKeyEventLocked(packageName, pid, uid, asSystemService, keyEvent, needWakeLock,
  4.             null, 0, false);
  5. }
复制

接下来看哪里调用了handleMediaKeyEventLocked()。

回调的地方在SessionManagerImpl.dispatchMediaKeyEvent()中。

SessionManagerImpl.dispatchMediaKeyEvent()
  1. @Override
  2. public void dispatchMediaKeyEvent(String packageName, boolean asSystemService,
  3.       KeyEvent keyEvent, boolean needWakeLock) {
  4. //略
  5.   try {
  6. //开机引导是否完成
  7. //之前写过过上下曲收不到就是这个原因
  8.       if (!isUserSetupComplete()) {
  9.           return;
  10.       }
  11.       synchronized (mLock) {
  12.           boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
  13. //没有全局的Session,而且全局的都是系统进程
  14.           if (isGlobalPriorityActive && uid != Process.SYSTEM_UID) {
  15.               return;
  16.           }
  17. //略
  18.           if (isGlobalPriorityActive) {
  19.               dispatchMediaKeyEventLocked(packageName, pid, uid, asSystemService,
  20.                       keyEvent, needWakeLock);
  21.           } else {
  22. //走这里
  23. //mMediaKeyEventHandler是KeyEventHandler对象
  24.               mMediaKeyEventHandler.handleMediaKeyEventLocked(packageName, pid, uid,
  25.                       asSystemService, keyEvent, needWakeLock);
  26.           }
  27.       }
  28.   } finally {
  29.       Binder.restoreCallingIdentity(token);
  30.   }
  31. }
复制

我们知道MediaSessionManager中的mSerivce就是SessionManagerImpl的代理。

MediaSessionManager.java

dispatchMediaKeyEventInternal()

这里调用了SessionManagerImpl中的dispatchMediaKeyEvent()

  1. private void dispatchMediaKeyEventInternal(KeyEvent keyEvent, boolean asSystemService,
  2.       boolean needWakeLock) {
  3.   try {
  4.       mService.dispatchMediaKeyEvent(mContext.getPackageName(), asSystemService, keyEvent,needWakeLock);
  5.   } catch (RemoteException e) {
  6.   }
  7. }
复制

在dispatchMediaKeyEventAsSystemService中有调用了dispatchMediaKeyEventInternal()。

dispatchMediaKeyEventAsSystemService()
  1. @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
  2. public void dispatchMediaKeyEventAsSystemService(@NonNull KeyEvent keyEvent) {
  3.   dispatchMediaKeyEventInternal(keyEvent, /*asSystemService=*/true, /*needWakeLock=*/true);
  4. }
复制

asSystemService这里默认传入的asSystemService就是为true

dispatchMediaKeyEventAsSystemService()的调用是在PhoneFallbackEventHandler()中。

PhoneFallbackEventHandler.java

  1. frameworks\base\core\java\com\android\internal\policy\PhoneFallbackEventHandler.java
复制
handleMediaKeyEvent()
  1. private void handleMediaKeyEvent(KeyEvent keyEvent) {
  2.   getMediaSessionManager().dispatchMediaKeyEventAsSystemService(keyEvent);
  3. }
复制

handleMediaKeyEvent()的调用分down和up,我们这里暂时看down。

onKeyUp()
  1. @UnsupportedAppUsage
  2. boolean onKeyDown(int keyCode, KeyEvent event) {
  3.   final KeyEvent.DispatcherState dispatcher = mView.getKeyDispatcherState();
  4.   switch (keyCode) {
  5.       case KeyEvent.KEYCODE_VOLUME_UP:
  6.       case KeyEvent.KEYCODE_VOLUME_DOWN:
  7.       case KeyEvent.KEYCODE_VOLUME_MUTE: {
  8. //处理音量
  9.           handleVolumeKeyEvent(event);
  10.           return true;
  11.       }
  12.       case KeyEvent.KEYCODE_MEDIA_PLAY:
  13.       case KeyEvent.KEYCODE_MEDIA_PAUSE:
  14.       case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
  15.       case KeyEvent.KEYCODE_MUTE:
  16.       case KeyEvent.KEYCODE_HEADSETHOOK:
  17.       case KeyEvent.KEYCODE_MEDIA_STOP:
  18.       case KeyEvent.KEYCODE_MEDIA_NEXT:
  19.       case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
  20.       case KeyEvent.KEYCODE_MEDIA_REWIND:
  21.       case KeyEvent.KEYCODE_MEDIA_RECORD:
  22.       case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
  23.       case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
  24. //这里处理大部分媒体按键
  25.           handleMediaKeyEvent(event);
  26.           return true;
  27.       }
  28. //略
  29.   }
  30.   return false;
  31. }
复制
dispatchKeyEvent()
  1. public boolean dispatchKeyEvent(KeyEvent event) {
  2.   final int action = event.getAction();
  3.   final int keyCode = event.getKeyCode();
  4.   if (action == KeyEvent.ACTION_DOWN) {
  5.       return onKeyDown(keyCode, event);
  6.   } else {
  7.       return onKeyUp(keyCode, event);
  8.   }
  9. }
复制

dispatchKeyEvent()的调用是在ViewRootImpl.java中,暂时跟到这里。

后续继续跟源码。

参考文章

自动化零件服务商 - 供应SMC,FESTO,CKD全新正品气动元件

相关文章

自动化零件服务商 - 供应SMC,FESTO,CKD全新正品气动元件

暂无评论

none
暂无评论...