Rich content insertion

最近Android 12 发布了一个新的功能,关于富文本的插入
具体关于API的使用可以看:Rich content insertion
可以看一下实现的效果:

实际可以分为两个部分:copy 和 paste,也分别是两个Application。上完这就涉及到了跨进程通信.
我们先铺垫一下,看一下如何通信的:

ClipboardService 系统

在这里插入图片描述

copy --> paste 到整个过程

  1. Copy action 是从 Coping Application 将数据copy到 ClipboardService.
  2. Paste action 是从 ClipboardService 将数据paste到 Pasting Application

进程通信

  • ClipboardService 是系统的服务,继承SystemService,
  • ClipboardService 与 Application 通过Binder通信
    通信协议看:outfit /soong/.intermediates/frameworks/base/framework-minus-apex/android_common/xref31/srcjars.xref/android/content/IClipboard.java

通信的方式具象化就是:copy和paste

// == copy ==
val text = "hello woddrld"
val mClipData = ClipData.newPlainText("test", text)
manager.setPrimaryClip(mClipData)

// == paste ==
val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
mClipData = manager.primaryClip

数据管理

  • ClipboardService 持有SparseArray<PerUserClipboard> 缓存数据,key 是 用户ID(多用户)

  • 通过 ClipboardManager 对 Clipboard 的数据进行管理, ClipboardManager 的具体实现是 ClipboardImpl frameworks/base/core/java/android/content/ClipboardManager.java

    ClipboardManager 通过 getSystemService(CLIPBOARD_SERVICE) as ClipboardManager获取.

数据类型

ClipboardService 持有SparseArray<PerUserClipboard> 缓存数据,key 是 用户ID(多用户)
可以看观察PerUserClipboard的 UML 图
在这里插入图片描述
可以发现,对于具体的内容,封装成Item.
item 可以有很多类型:text,uri,intent.
存储于ClipData 的 mItems的字段中。

权限管理

先看哪里会对权限检查

 public void setPrimaryClip(ClipData clip, String callingPackage, 
 							@UserIdInt int userId) {
    synchronized (this) {
        if (clip == null || clip.getItemCount() <= 0) {
            throw new IllegalArgumentException("No items");
        }
        final int intendingUid = getIntendingUid(callingPackage, userId);
        final int intendingUserId = UserHandle.getUserId(intendingUid);
        if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
                    intendingUid, intendingUserId)) {
            return;
        }
        // 注意这里, 继续追下去
        checkDataOwnerLocked(clip, intendingUid);
        setPrimaryClipInternal(clip, intendingUid);
    }
}

private final void checkUriOwnerLocked(Uri uri, int sourceUid) {
  if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()))
  return;

  final long ident = Binder.clearCallingIdentity();
  try {
      // 看到这里
      mUgmInternal.checkGrantUriPermission(sourceUid, null,
              ContentProvider.getUriWithoutUserId(uri),
              Intent.FLAG_GRANT_READ_URI_PERMISSION,
             ContentProvider.getUserIdFromUri(uri,UserHandle.getUserId(sourceUid)));
  } finally {
      Binder.restoreCallingIdentity(ident);
  }
}

具体的权限检查需要看到:UriGrantsManagerService 中的 checkGrantUriPermission 方法.
这里注意这个flag:Intent.FLAG_GRANT_READ_URI_PERMISSION

Copy 和 Paste menu 的构建

在这里插入图片描述

这里描述了两个动作的时序图:

  1. 长按空白弹出paste menu,也就是FloatingToolbar.
  2. 选择文字弹出copy menu,也就是FloatingToolbar.

两个动作最终都是从 Editor 调用TextView 的 startActionMode() 来展现 FloatingToolbar.

区别在于,选择文字弹出copy menu的过程中,需要将文字设置背景色.


需要注意的是:FloatingToolbar 在layout的时候,会设置menu item点击的监听器(MenuItem.OnMenuItemClickListener).

Rich text insert

综上所述:

当我们从一个应用copy到另外一个应用时,是通过ClipboardService通信,当要执行copy或者paste的时候,会弹出FlotingToobar.

点击menu item 的时候会通过MenuItem.OnMenuItemClickListener 回调。

到这里我们看一下官方API给我们暴露出的接口:

在这里插入图片描述
结合Google的commit记录来看:
https://android-review.googlesource.com/c/platform/frameworks/support/+/1306703
https://android-review.googlesource.com/c/platform/frameworks/support/+/1510258

三个action,复写三个方法,做了三件事:

  1. 检查是否需要回调
  2. 构建ContentInfoCompat
  3. 回调处理

从AppCompatEditText 中 调用到 AppCompatReceiveContentHelper 类中

public boolean onTextContextMenuItem(int id) {
  	// 判断是否要回调通知,如果需要则执行回调
    if (maybeHandleMenuActionViaPerformReceiveContent(this, id)) {
        return true;
    }
    return super.onTextContextMenuItem(id);
}

@Override
public boolean onDragEvent(@SuppressWarnings("MissingNullability") DragEvent event) {
  if (maybeHandleDragEventViaPerformReceiveContent(this, event)) {
    return true;
  }
  return super.onDragEvent(event);
}

public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
  InputConnection ic = super.onCreateInputConnection(outAttrs);
  mTextHelper.populateSurroundingTextIfNeeded(this, ic, outAttrs);
  ic = AppCompatHintHelper.onCreateInputConnection(ic, outAttrs, this);

  String[] mimeTypes = ViewCompat.getOnReceiveContentMimeTypes(this);
  if (ic != null && mimeTypes != null) {
    EditorInfoCompat.setContentMimeTypes(outAttrs, mimeTypes);
    OnCommitContentListener onCommitContentListener = createOnCommitContentListener(this);
    ic = InputConnectionCompat.createWrapper(ic, outAttrs, onCommitContentListener);
  }
  return ic;
}

这里复写了三个方法,对应着三个action:

  • SOURCE_CLIPBOARD
  • SOURCE_INPUT_METHOD
  • SOURCE_DRAG_AND_DROP

总结

这里我们梳理了三块知识点:

  1. ClipboardService 作为banner的实现者,通信的代理.
    • ClipboardService 如何管理缓存数据: SparseArray
    • PerUserClipboard 和 ClipData 的数据结构
    • ClipData 中存在URI 的访问权限
    • ClipboardService 抽象出来的管理者:ClipboardManager
  2. FlotingToolbar menu 的构建menu item,展现toolbar,设置menu点击事件.
  3. Rich text insert 通过对 输入,FlotingToolbar的点击事件,拖拽事件的监听,构造一个新的回调接口:OnReceiveContentListener:onReceiveContent

Resource link

api:
https://developer.android.com/about/versions/12/features/unified-content-api
https://joebirch.co/android/exploring-android-12-unified-rich-content-api/
https://developer.android.com/guide/topics/text/copy-paste?hl=zh-cn

blog:
https://www.cnblogs.com/mengdd/p/3572316.html
https://blog.csdn.net/qq475703980/article/details/89061293

android 实现剪贴板的粘贴复制
https://blog.csdn.net/uniquemei/article/details/52824000

commit change 记录:

https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java;bpv=1;bpt=0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值