1package com.genymobile.scrcpy.wrappers;
2
3import com.genymobile.scrcpy.FakeContext;
4import com.genymobile.scrcpy.Ln;
5
6import android.content.ClipData;
7import android.content.IOnPrimaryClipChangedListener;
8import android.os.Build;
9import android.os.IInterface;
10
11import java.lang.reflect.InvocationTargetException;
12import java.lang.reflect.Method;
13
14public final class ClipboardManager {
15 private final IInterface manager;
16 private Method getPrimaryClipMethod;
17 private Method setPrimaryClipMethod;
18 private Method addPrimaryClipChangedListener;
19 private int getMethodVersion;
20 private int setMethodVersion;
21 private int addListenerMethodVersion;
22
23 public ClipboardManager(IInterface manager) {
24 this.manager = manager;
25 }
26
27 private Method getGetPrimaryClipMethod() throws NoSuchMethodException {
28 if (getPrimaryClipMethod == null) {
29 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
30 getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class);
31 } else {
32 try {
33 getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class);
34 getMethodVersion = 0;
35 } catch (NoSuchMethodException e1) {
36 try {
37 getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class);
38 getMethodVersion = 1;
39 } catch (NoSuchMethodException e2) {
40 try {
41 getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, String.class, int.class, int.class);
42 getMethodVersion = 2;
43 } catch (NoSuchMethodException e3) {
44 try {
45 getPrimaryClipMethod = manager.getClass().getMethod("getPrimaryClip", String.class, int.class, String.class);
46 getMethodVersion = 3;
47 } catch (NoSuchMethodException e4) {
48 getPrimaryClipMethod = manager.getClass()
49 .getMethod("getPrimaryClip", String.class, String.class, int.class, int.class, boolean.class);
50 getMethodVersion = 4;
51 }
52 }
53 }
54 }
55 }
56 }
57 return getPrimaryClipMethod;
58 }
59
60 private Method getSetPrimaryClipMethod() throws NoSuchMethodException {
61 if (setPrimaryClipMethod == null) {
62 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
63 setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class);
64 } else {
65 try {
66 setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, int.class);
67 setMethodVersion = 0;
68 } catch (NoSuchMethodException e1) {
69 try {
70 setPrimaryClipMethod = manager.getClass().getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class);
71 setMethodVersion = 1;
72 } catch (NoSuchMethodException e2) {
73 setPrimaryClipMethod = manager.getClass()
74 .getMethod("setPrimaryClip", ClipData.class, String.class, String.class, int.class, int.class);
75 setMethodVersion = 2;
76 }
77 }
78 }
79 }
80 return setPrimaryClipMethod;
81 }
82
83 private static ClipData getPrimaryClip(Method method, int methodVersion, IInterface manager)
84 throws InvocationTargetException, IllegalAccessException {
85 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
86 return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME);
87 }
88
89 switch (methodVersion) {
90 case 0:
91 return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
92 case 1:
93 return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
94 case 2:
95 return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
96 case 3:
97 return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID, null);
98 default:
99 // The last boolean parameter is "userOperate"
100 return (ClipData) method.invoke(manager, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0, true);
101 }
102 }
103
104 private static void setPrimaryClip(Method method, int methodVersion, IInterface manager, ClipData clipData)
105 throws InvocationTargetException, IllegalAccessException {
106 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
107 method.invoke(manager, clipData, FakeContext.PACKAGE_NAME);
108 return;
109 }
110
111 switch (methodVersion) {
112 case 0:
113 method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
114 break;
115 case 1:
116 method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
117 break;
118 default:
119 method.invoke(manager, clipData, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
120 break;
121 }
122 }
123
124 public CharSequence getText() {
125 try {
126 Method method = getGetPrimaryClipMethod();
127 ClipData clipData = getPrimaryClip(method, getMethodVersion, manager);
128 if (clipData == null || clipData.getItemCount() == 0) {
129 return null;
130 }
131 return clipData.getItemAt(0).getText();
132 } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
133 Ln.e("Could not invoke method", e);
134 return null;
135 }
136 }
137
138 public boolean setText(CharSequence text) {
139 try {
140 Method method = getSetPrimaryClipMethod();
141 ClipData clipData = ClipData.newPlainText(null, text);
142 setPrimaryClip(method, setMethodVersion, manager, clipData);
143 return true;
144 } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
145 Ln.e("Could not invoke method", e);
146 return false;
147 }
148 }
149
150 private static void addPrimaryClipChangedListener(Method method, int methodVersion, IInterface manager, IOnPrimaryClipChangedListener listener)
151 throws InvocationTargetException, IllegalAccessException {
152 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
153 method.invoke(manager, listener, FakeContext.PACKAGE_NAME);
154 return;
155 }
156
157 switch (methodVersion) {
158 case 0:
159 method.invoke(manager, listener, FakeContext.PACKAGE_NAME, FakeContext.ROOT_UID);
160 break;
161 case 1:
162 method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID);
163 break;
164 default:
165 method.invoke(manager, listener, FakeContext.PACKAGE_NAME, null, FakeContext.ROOT_UID, 0);
166 break;
167 }
168 }
169
170 private Method getAddPrimaryClipChangedListener() throws NoSuchMethodException {
171 if (addPrimaryClipChangedListener == null) {
172 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
173 addPrimaryClipChangedListener = manager.getClass()
174 .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class);
175 } else {
176 try {
177 addPrimaryClipChangedListener = manager.getClass()
178 .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, int.class);
179 addListenerMethodVersion = 0;
180 } catch (NoSuchMethodException e1) {
181 try {
182 addPrimaryClipChangedListener = manager.getClass()
183 .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class,
184 int.class);
185 addListenerMethodVersion = 1;
186 } catch (NoSuchMethodException e2) {
187 addPrimaryClipChangedListener = manager.getClass()
188 .getMethod("addPrimaryClipChangedListener", IOnPrimaryClipChangedListener.class, String.class, String.class,
189 int.class, int.class);
190 addListenerMethodVersion = 2;
191 }
192 }
193 }
194 }
195 return addPrimaryClipChangedListener;
196 }
197
198 public boolean addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener) {
199 try {
200 Method method = getAddPrimaryClipChangedListener();
201 addPrimaryClipChangedListener(method, addListenerMethodVersion, manager, listener);
202 return true;
203 } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
204 Ln.e("Could not invoke method", e);
205 return false;
206 }
207 }
208}
209