1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.core.view.inputmethod;
18 
19 import android.content.ClipDescription;
20 import android.net.Uri;
21 import android.os.Build;
22 import android.view.inputmethod.InputContentInfo;
23 
24 import androidx.annotation.RequiresApi;
25 
26 import org.jspecify.annotations.NonNull;
27 import org.jspecify.annotations.Nullable;
28 
29 /**
30  * Helper for accessing features in InputContentInfo introduced after API level 13 in a backwards
31  * compatible fashion.
32  */
33 public final class InputContentInfoCompat {
34 
35     private interface InputContentInfoCompatImpl {
getContentUri()36         @NonNull Uri getContentUri();
37 
getDescription()38         @NonNull ClipDescription getDescription();
39 
getLinkUri()40         @Nullable Uri getLinkUri();
41 
getInputContentInfo()42         @Nullable Object getInputContentInfo();
43 
requestPermission()44         void requestPermission();
45 
releasePermission()46         void releasePermission();
47     }
48 
49     private static final class InputContentInfoCompatBaseImpl
50             implements InputContentInfoCompatImpl {
51         private final @NonNull Uri mContentUri;
52         private final @NonNull ClipDescription mDescription;
53         private final @Nullable Uri mLinkUri;
54 
InputContentInfoCompatBaseImpl(@onNull Uri contentUri, @NonNull ClipDescription description, @Nullable Uri linkUri)55         InputContentInfoCompatBaseImpl(@NonNull Uri contentUri,
56                 @NonNull ClipDescription description, @Nullable Uri linkUri) {
57             mContentUri = contentUri;
58             mDescription = description;
59             mLinkUri = linkUri;
60         }
61 
62         @Override
getContentUri()63         public @NonNull Uri getContentUri() {
64             return mContentUri;
65         }
66 
67         @Override
getDescription()68         public @NonNull ClipDescription getDescription() {
69             return mDescription;
70         }
71 
72         @Override
getLinkUri()73         public @Nullable Uri getLinkUri() {
74             return mLinkUri;
75         }
76 
77         @Override
getInputContentInfo()78         public @Nullable Object getInputContentInfo() {
79             return null;
80         }
81 
82         @Override
requestPermission()83         public void requestPermission() {
84         }
85 
86         @Override
releasePermission()87         public void releasePermission() {
88         }
89     }
90 
91     @RequiresApi(25)
92     private static final class InputContentInfoCompatApi25Impl
93             implements InputContentInfoCompatImpl {
94         final @NonNull InputContentInfo mObject;
95 
InputContentInfoCompatApi25Impl(@onNull Object inputContentInfo)96         InputContentInfoCompatApi25Impl(@NonNull Object inputContentInfo) {
97             mObject = (InputContentInfo) inputContentInfo;
98         }
99 
InputContentInfoCompatApi25Impl(@onNull Uri contentUri, @NonNull ClipDescription description, @Nullable Uri linkUri)100         InputContentInfoCompatApi25Impl(@NonNull Uri contentUri,
101                 @NonNull ClipDescription description, @Nullable Uri linkUri) {
102             mObject = new InputContentInfo(contentUri, description, linkUri);
103         }
104 
105         @Override
getContentUri()106         public @NonNull Uri getContentUri() {
107             return mObject.getContentUri();
108         }
109 
110         @Override
getDescription()111         public @NonNull ClipDescription getDescription() {
112             return mObject.getDescription();
113         }
114 
115         @Override
getLinkUri()116         public @Nullable Uri getLinkUri() {
117             return mObject.getLinkUri();
118         }
119 
120         @Override
getInputContentInfo()121         public @NonNull Object getInputContentInfo() {
122             return mObject;
123         }
124 
125         @Override
requestPermission()126         public void requestPermission() {
127             mObject.requestPermission();
128         }
129 
130         @Override
releasePermission()131         public void releasePermission() {
132             mObject.releasePermission();
133         }
134     }
135 
136     private final InputContentInfoCompatImpl mImpl;
137 
138     /**
139      * Constructs {@link InputContentInfoCompat}.
140      *
141      * @param contentUri content URI to be exported from the input method. This cannot be
142      *                   {@code null}.
143      * @param description a {@link ClipDescription} object that contains the metadata of
144      *                    {@code contentUri} such as MIME type(s). This object cannot be
145      *                    {@code null}. Also {@link ClipDescription#getLabel()} should be describing
146      *                    the content specified by {@code contentUri} for accessibility reasons.
147      * @param linkUri an optional {@code http} or {@code https} URI. The editor author may provide
148      *                a way to navigate the user to the specified web page if this is not
149      *                {@code null}.
150      */
InputContentInfoCompat(@onNull Uri contentUri, @NonNull ClipDescription description, @Nullable Uri linkUri)151     public InputContentInfoCompat(@NonNull Uri contentUri,
152             @NonNull ClipDescription description, @Nullable Uri linkUri) {
153         if (Build.VERSION.SDK_INT >= 25) {
154             mImpl = new InputContentInfoCompatApi25Impl(contentUri, description, linkUri);
155         } else {
156             mImpl = new InputContentInfoCompatBaseImpl(contentUri, description, linkUri);
157         }
158     }
159 
InputContentInfoCompat(@onNull InputContentInfoCompatImpl impl)160     private InputContentInfoCompat(@NonNull InputContentInfoCompatImpl impl) {
161         mImpl = impl;
162     }
163 
164     /**
165      * @return content URI with which the content can be obtained.
166      */
getContentUri()167     public @NonNull Uri getContentUri() {
168         return mImpl.getContentUri();
169     }
170 
171     /**
172      * @return {@link ClipDescription} object that contains the metadata of {@code #getContentUri()}
173      * such as MIME type(s). {@link ClipDescription#getLabel()} can be used for accessibility
174      * purpose.
175      */
getDescription()176     public @NonNull ClipDescription getDescription() {
177         return mImpl.getDescription();
178     }
179 
180     /**
181      * @return an optional {@code http} or {@code https} URI that is related to this content.
182      */
getLinkUri()183     public @Nullable Uri getLinkUri() {
184         return mImpl.getLinkUri();
185     }
186 
187     /**
188      * Creates an instance from a framework android.view.inputmethod.InputContentInfo object.
189      *
190      * <p>This method always returns {@code null} on API &lt;= 24.</p>
191      *
192      * @param inputContentInfo an android.view.inputmethod.InputContentInfo object, or {@code null}
193      *                         if none.
194      * @return an equivalent {@link InputContentInfoCompat} object, or {@code null} if not
195      * supported.
196      */
wrap(@ullable Object inputContentInfo)197     public static @Nullable InputContentInfoCompat wrap(@Nullable Object inputContentInfo) {
198         if (inputContentInfo == null) {
199             return null;
200         }
201         if (Build.VERSION.SDK_INT < 25) {
202             return null;
203         }
204         return new InputContentInfoCompat(new InputContentInfoCompatApi25Impl(inputContentInfo));
205     }
206 
207     /**
208      * Gets the underlying framework android.view.inputmethod.InputContentInfo object.
209      *
210      * <p>This method always returns {@code null} on API &lt;= 24.</p>
211      *
212      * @return an equivalent android.view.inputmethod.InputContentInfo object, or {@code null} if
213      * not supported.
214      */
unwrap()215     public @Nullable Object unwrap() {
216         return mImpl.getInputContentInfo();
217     }
218 
219     /**
220      * Requests a temporary read-only access permission for content URI associated with this object.
221      *
222      * <p>The lifecycle of the permission granted here is tied to this object instance. If the
223      * permission is not released explicitly via {@link #releasePermission()}, it will be
224      * released automatically when there are no more references to this object.</p>
225      *
226      * <p>Does nothing if the temporary permission is already granted.</p>
227      */
requestPermission()228     public void requestPermission() {
229         mImpl.requestPermission();
230     }
231 
232     /**
233      * Releases a temporary read-only access permission for content URI associated with this object.
234      *
235      * <p>Does nothing if the temporary permission is not granted.</p>
236      */
releasePermission()237     public void releasePermission() {
238         mImpl.releasePermission();
239     }
240 }
241