diff --git a/app-catalog/samples/foundation/build.gradle b/app-catalog/samples/foundation/build.gradle index 7813486..faaecee 100644 --- a/app-catalog/samples/foundation/build.gradle +++ b/app-catalog/samples/foundation/build.gradle @@ -1,5 +1,7 @@ apply from: "$rootDir/gradle/sample-build.gradle" +apply plugin: 'kotlin-parcelize' + android { sourceSets { main { diff --git a/app-catalog/samples/foundation/src/main/AndroidManifest.xml b/app-catalog/samples/foundation/src/main/AndroidManifest.xml index b185f15..bf913d8 100644 --- a/app-catalog/samples/foundation/src/main/AndroidManifest.xml +++ b/app-catalog/samples/foundation/src/main/AndroidManifest.xml @@ -354,10 +354,20 @@ + + + + \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/Book.aidl b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/Book.aidl new file mode 100644 index 0000000..3823130 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/Book.aidl @@ -0,0 +1,5 @@ +package com.wintmain.foundation.aidl; + +import com.wintmain.foundation.aidl.Book; + +parcelable Book; diff --git a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/INewPeopleListener.aidl b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/INewPeopleListener.aidl similarity index 66% rename from app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/INewPeopleListener.aidl rename to app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/INewPeopleListener.aidl index 79bdb21..a78b558 100644 --- a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/INewPeopleListener.aidl +++ b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/INewPeopleListener.aidl @@ -1,7 +1,7 @@ // INewPeopleListener.aidl -package com.wintmain.foundation; +package com.wintmain.foundation.aidl; -import com.wintmain.foundation.People; +import com.wintmain.foundation.aidl.People; // AIDL中无法使用普通接口,所以创建此文件 diff --git a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/IPeopleManager.aidl b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/IPeopleManager.aidl similarity index 75% rename from app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/IPeopleManager.aidl rename to app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/IPeopleManager.aidl index 205e754..289bb3d 100644 --- a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/IPeopleManager.aidl +++ b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/IPeopleManager.aidl @@ -1,11 +1,12 @@ // IPeopleManager.aidl // AIDL接口中只支持方法,不支持声明静态常量 -package com.wintmain.foundation; +package com.wintmain.foundation.aidl; // 自定义的Parcelable对象和AIDL对象不管是否和当前的AIDL文件位于同一个包内,也必须要显式import进来 -import com.wintmain.foundation.People; -import com.wintmain.foundation.INewPeopleListener; +import com.wintmain.foundation.aidl.Book; +import com.wintmain.foundation.aidl.People; +import com.wintmain.foundation.aidl.INewPeopleListener; interface IPeopleManager { List getPeopleList(); @@ -13,6 +14,8 @@ interface IPeopleManager { // 我们需根据实现需要去指定参数类型,因为这在底层实现是有开销的。 void addPeople(in People people); + void addPeopleAndBook(in People people, in Book book); + // 扩展新增代码:新增接口 void registerListener(INewPeopleListener listener); void unregisterListener(INewPeopleListener listener); diff --git a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/People.aidl b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/People.aidl similarity index 88% rename from app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/People.aidl rename to app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/People.aidl index d27e4c5..8e23aa8 100644 --- a/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/People.aidl +++ b/app-catalog/samples/foundation/src/main/aidl/com/wintmain/foundation/aidl/People.aidl @@ -1,5 +1,5 @@ // People.aidl -package com.wintmain.foundation; +package com.wintmain.foundation.aidl; // Declare any non-default types here with import statements @@ -14,7 +14,7 @@ package com.wintmain.foundation; // Parcelable的作用是序列化对象,因为跨进程通信传输对象必须是以序列化和反序列化的方式进行。 // Parcelable的实现有些繁琐,但性能效率很高。 -import com.wintmain.foundation.People; +import com.wintmain.foundation.aidl.People; -// People should be declared in a file called com/wintmain/foundation/People.aidl +// People should be declared in a file called com/wintmain/foundation/aidl/People.aidl parcelable People; diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PlaceHolderActivity.java b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PlaceHolderActivity.java index cdc2af3..42cf032 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PlaceHolderActivity.java +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PlaceHolderActivity.java @@ -20,10 +20,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.os.Bundle; -import android.os.IBinder; -import android.os.RemoteException; import android.provider.AlarmClock; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; @@ -39,19 +36,12 @@ description = "一个为了测试API调用的入口", tags = "A-Self_demos") public class PlaceHolderActivity extends AppCompatActivity { - private static final String TAG = PlaceHolderActivity.class.getSimpleName(); - IPeopleManager peopleManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.pft_activity_main); - // AIDL示例 S - Intent intent = new Intent(this, PeopleRemoteService.class); - bindService(intent, mConnection, BIND_AUTO_CREATE); - // AIDL示例 E - // 初始化一些三方库 initLibs(getApplication()); @@ -87,20 +77,6 @@ public void onRightClick(TitleBarExt titleBar) { }); } - @Override - protected void onDestroy() { - // 移除监听 - if (peopleManager != null && peopleManager.asBinder().isBinderAlive()) { - try { - peopleManager.unregisterListener(mNewPeopleListener); - } catch (RemoteException e) { - android.util.Log.d(TAG, "catch:" + e.getMessage()); - } - } - unbindService(mConnection); - super.onDestroy(); - } - private void initLibs(Application application) { // 初始化 TitleBar 默认样式 TitleBarExt.setDefaultStyle( @@ -124,34 +100,4 @@ public TextView newRightView(Context context) { // 初始化 Toast ToastUtils.init(this.getApplication()); } - - - private final ServiceConnection mConnection = new ServiceConnection() { - public void onServiceConnected(ComponentName className, IBinder service) { - android.util.Log.d(TAG, "onServiceConnected..."); - peopleManager = IPeopleManager.Stub.asInterface(service); - try { - People people = new People(1000, "wintmain"); - // 注册监听 - peopleManager.registerListener(mNewPeopleListener); - peopleManager.addPeople(people); - } catch (RemoteException e) { - android.util.Log.d(TAG, "catch:" + e.getMessage()); - } - } - public void onServiceDisconnected(ComponentName className) { - android.util.Log.d(TAG, "onServiceDisconnected!!!"); - ToastUtils.show("onServiceDisconnected!!!"); - peopleManager = null; - } - }; - - - // 定义监听接口 - private final INewPeopleListener mNewPeopleListener = new INewPeopleListener.Stub() { - @Override - public void onNewPeople(People newPeople) { - ToastUtils.show("onNewPeople: " + newPeople); - } - }; } diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/AidlSampleActivity.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/AidlSampleActivity.kt new file mode 100644 index 0000000..0a96d68 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/AidlSampleActivity.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2023-2025 wintmain + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wintmain.foundation.aidl + +import android.content.ComponentName +import android.content.Intent +import android.content.ServiceConnection +import android.os.Bundle +import android.os.IBinder +import android.os.RemoteException +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import com.google.android.catalog.framework.annotations.Sample +import lib.wintmain.toaster.toast.ToastUtils + +@Sample(name = "AIDL-SampleActivity", description = "AIDL简单示例", tags =["A-Self_demos"]) +class AidlSampleActivity: AppCompatActivity() { + companion object { + private const val TAG = "AidlSampleActivity" + } + var peopleManager: IPeopleManager? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 初始化 Toast + ToastUtils.init(this.application) + + // 页面打开的时候就开始绑定服务 + val intent = Intent(this, PeopleRemoteService::class.java) + bindService(intent, mConnection, BIND_AUTO_CREATE) + } + + override fun onDestroy() { + // 移除监听 + peopleManager?.let { manager -> + if (manager.asBinder().isBinderAlive) { + try { + manager.unregisterListener(mNewPeopleListener) + } catch (e: RemoteException) { + Log.d(TAG, "catch:" + e.message) + } + } + } + unbindService(mConnection) + super.onDestroy() + } + + private val mConnection: ServiceConnection = object : ServiceConnection { + override fun onServiceConnected(className: ComponentName, service: IBinder) { + Log.d(TAG, "onServiceConnected...") + peopleManager = IPeopleManager.Stub.asInterface(service) + peopleManager?.run { + try { + val people = People(1000, "wintmain") + val book = Book(1, "Book") + // 注册监听 + registerListener(mNewPeopleListener) + addPeople(people) + addPeopleAndBook(people, book) + } catch (e: RemoteException) { + Log.d(TAG, "catch:" + e.message) + } + } + } + + override fun onServiceDisconnected(className: ComponentName) { + Log.d(TAG, "onServiceDisconnected!!!") + ToastUtils.show("onServiceDisconnected!!!") + peopleManager = null + } + } + + // 定义监听接口 + private val mNewPeopleListener: INewPeopleListener = object : INewPeopleListener.Stub() { + override fun onNewPeople(newPeople: People) { + ToastUtils.show("onNewPeople: $newPeople") + } + } +} \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/Book.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/Book.kt new file mode 100644 index 0000000..9c1535c --- /dev/null +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/Book.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2023-2025 wintmain + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wintmain.foundation.aidl + +import android.os.Parcelable +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class Book( + val pId: Int, + val pName: String +) : Parcelable diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/People.java b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/People.java similarity index 98% rename from app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/People.java rename to app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/People.java index 6c386c7..ff7d26c 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/People.java +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/People.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.wintmain.foundation; +package com.wintmain.foundation.aidl; import android.os.Parcel; import android.os.Parcelable; diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PeopleRemoteService.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/PeopleRemoteService.kt similarity index 90% rename from app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PeopleRemoteService.kt rename to app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/PeopleRemoteService.kt index 8c38ab2..ff6cc5b 100644 --- a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/PeopleRemoteService.kt +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/aidl/PeopleRemoteService.kt @@ -14,16 +14,17 @@ * limitations under the License. */ -package com.wintmain.foundation +package com.wintmain.foundation.aidl import android.app.Service import android.content.Intent import android.os.Binder import android.os.IBinder import android.os.RemoteCallbackList +import android.util.Log import java.util.concurrent.CopyOnWriteArrayList -// 服务端代码,客户端见[PlaceHoldActivity.java] +// 服务端代码,客户端见[AidlSampleActivity.java] class PeopleRemoteService : Service() { val mPeopleList = CopyOnWriteArrayList() // 客户端注册和移除注册过程中使用的虽是同一个客户端对象, @@ -47,6 +48,10 @@ class PeopleRemoteService : Service() { mListenList.finishBroadcast() } + override fun addPeopleAndBook(people: People, book: Book) { + Log.d("wintmain", "addPeopleAndBook: people = $people, book = $book") + } + // RemoteCallbackList是系统专门提供的用于删除跨进程listener接口,它是一个泛型,支持管理任意AIDL接口。 // 另外它内部自动实现了线程同步的功能。 // beginBroadcast和finishBroadcast必须要配对使用,哪怕仅仅获取RemoteCallbackList中的元素个数 diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/webview/CustomActionWebView.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/webview/CustomActionWebView.kt new file mode 100644 index 0000000..5ab7915 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/webview/CustomActionWebView.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2023-2025 wintmain + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wintmain.foundation.webview + +import android.content.Context +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import android.util.AttributeSet +import android.view.ActionMode +import android.view.ActionMode.Callback +import android.webkit.JavascriptInterface +import android.webkit.WebView +import androidx.core.view.get +import androidx.core.view.size + +class CustomActionWebView( + context: Context, + attrs: AttributeSet?, +) : WebView(context, attrs) { + companion object { + private const val TAG = "CustomActionWebView" + } + + private var mActionMode: ActionMode? = null + private var mActionList: List = ArrayList() + private var mActionSelectListener: ActionSelectListener? = null + + /** + * 处理item,处理点击 + * @param actionMode + */ + private fun resolveActionMode(actionMode: ActionMode?): ActionMode { + if (actionMode != null) { + val menu = actionMode.menu + mActionMode = actionMode + menu.clear() + for (i in mActionList.indices) { + menu.add(mActionList[i]) + } + for (i in 0.. + (item.title as String?)?.let { getSelectedData(it) } + releaseAction() + true + } + } + } + mActionMode = actionMode!! + return actionMode + } + + override fun startActionMode(callback: Callback?): ActionMode { + val actionMode = super.startActionMode(callback) + return resolveActionMode(actionMode) + } + + override fun startActionMode(callback: Callback?, type: Int): ActionMode { + val actionMode = super.startActionMode(callback, type) + return resolveActionMode(actionMode) + } + + private fun releaseAction() { + if (mActionMode != null) { + mActionMode!!.finish() + mActionMode = null + } + } + + /** + * 点击的时候,获取网页中选择的文本,回掉到原生中的js接口 + * @param title 传入点击的item文本,一起通过js返回给原生接口 + * IE9以下支持:document.selection    + * IE9、Firefox、Safari、Chrome和Opera支持:window.getSelection() + */ + private fun getSelectedData(title: String) { + val js = "(function getSelectedText() {" + + "var txt1 =window.getSelection().getRangeAt(0).startContainer.data;" + + "var txt2 =window.getSelection().toString();" + + "var title = \"" + title + "\";" + + "JSInterface.callback(txt1,txt2,title);" + + "})()" + if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) { + evaluateJavascript("javascript:$js", null) + } else { + loadUrl("javascript:$js") + } + } + + fun linkJSInterface() { + addJavascriptInterface(ActionSelectInterface(), "JSInterface") + } + + /** + * 设置弹出action列表 + * @param actionList + */ + fun setActionList(actionList: MutableList) { + mActionList = actionList + } + + /** + * 设置点击回掉 + * @param actionSelectListener + */ + fun setActionSelectListener(actionSelectListener: ActionSelectListener?) { + this.mActionSelectListener = actionSelectListener + } + + /** + * 隐藏消失Action + */ + fun dismissAction() { + releaseAction() + } + + /** + * js选中的回掉接口 + */ + private inner class ActionSelectInterface { + @JavascriptInterface + fun callback(parentText: String?, value: String?, title: String?) { + this@CustomActionWebView.mActionSelectListener?.onClick(title, value, parentText) + } + } +} + +interface ActionSelectListener { + fun onClick(title: String?, parentText: String?, selectText: String?) +} diff --git a/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/webview/CustomActionWebViewActivity.kt b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/webview/CustomActionWebViewActivity.kt new file mode 100644 index 0000000..1a7eea2 --- /dev/null +++ b/app-catalog/samples/foundation/src/main/java/com/wintmain/foundation/webview/CustomActionWebViewActivity.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2023-2025 wintmain + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.wintmain.foundation.webview + +import android.os.Bundle +import android.webkit.WebSettings.LayoutAlgorithm.SINGLE_COLUMN +import android.webkit.WebViewClient +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import com.google.android.catalog.framework.annotations.Sample +import com.wintmain.foundation.R + +@Sample(name = "CustomActionWebViewActivity", description = "", tags = ["webkit"]) +class CustomActionWebViewActivity : AppCompatActivity() { + private lateinit var mWebView: CustomActionWebView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.custom_action_webview_activity) + mWebView = findViewById(R.id.webview) + + val list: MutableList = ArrayList() + list.add("复制") + list.add("分享") + list.add("批注") + //设置item + mWebView.setActionList(list) + + //链接js注入接口,使能选中返回数据 + mWebView.linkJSInterface() + + //增加点击回调 + mWebView.setActionSelectListener(object : ActionSelectListener { + override fun onClick(title: String?, parentText: String?, selectText: String?) { + val info = "内容: $selectText。\n段落: $parent" + Toast.makeText(applicationContext, info, Toast.LENGTH_LONG).show() + } + }) + + // 开启 javascript 渲染 + val webSettings = mWebView.getSettings() + + webSettings.javaScriptCanOpenWindowsAutomatically = true + + //关键点 + webSettings.useWideViewPort = true + + webSettings.layoutAlgorithm = SINGLE_COLUMN + + webSettings.displayZoomControls = false + + // 设置支持javascript脚本 + webSettings.javaScriptEnabled = true + + // 允许访问文件 + webSettings.allowFileAccess = true + + // 设置显示缩放按钮 + webSettings.builtInZoomControls = true + + // 支持缩放 + webSettings.setSupportZoom(true) + + webSettings.loadWithOverviewMode = true + + mWebView.setWebViewClient(WebViewClient()) + + // 载入内容 + // mWebView.loadUrl("file:///android_asset/111.html") + mWebView.loadData("qweweeeeeeeeeeqewq", "text/html", "utf-8") + } +} \ No newline at end of file diff --git a/app-catalog/samples/foundation/src/main/res/layout/custom_action_webview_activity.xml b/app-catalog/samples/foundation/src/main/res/layout/custom_action_webview_activity.xml new file mode 100644 index 0000000..0581a3d --- /dev/null +++ b/app-catalog/samples/foundation/src/main/res/layout/custom_action_webview_activity.xml @@ -0,0 +1,38 @@ + + + + +