1 /*
2  * Copyright (C) 2018 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 com.android.internal.inputmethod;
18 
19 import android.annotation.AnyThread;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.os.IBinder;
23 
24 import com.android.internal.annotations.GuardedBy;
25 
26 import java.lang.ref.WeakReference;
27 import java.util.WeakHashMap;
28 
29 /**
30  * A weak-reference-based mapper from IME token to {@link InputMethodPrivilegedOperations} that is
31  * used only to support deprecated IME APIs in {@link android.view.inputmethod.InputMethodManager}.
32  *
33  * <p>This class is designed to be used as a per-process global registry.</p>
34  */
35 public final class InputMethodPrivilegedOperationsRegistry {
InputMethodPrivilegedOperationsRegistry()36     private InputMethodPrivilegedOperationsRegistry() {
37         // Not intended to be instantiated.
38     }
39 
40     private static final Object sLock = new Object();
41 
42     @Nullable
43     @GuardedBy("sLock")
44     private static WeakHashMap<IBinder, WeakReference<InputMethodPrivilegedOperations>> sRegistry;
45 
46     @Nullable
47     private static InputMethodPrivilegedOperations sNop;
48 
49     @NonNull
50     @AnyThread
getNopOps()51     private static InputMethodPrivilegedOperations getNopOps() {
52         // Strict thread-safety is not necessary because temporarily creating multiple nop instance
53         // is basically harmless
54         if (sNop == null) {
55             sNop = new InputMethodPrivilegedOperations();
56         }
57         return sNop;
58     }
59 
60     /**
61      * Put a new entry to the registry.
62      *
63      * <p>Note: {@link InputMethodPrivilegedOperationsRegistry} does not hold strong reference to
64      * {@code token} and {@code ops}.  The caller must be responsible for holding strong references
65      * to those objects, that is until {@link android.inputmethodservice.InputMethodService} is
66      * destroyed.</p>
67      *
68      * @param token IME token
69      * @param ops {@link InputMethodPrivilegedOperations} to be associated with the given IME token
70      */
71     @AnyThread
put(IBinder token, InputMethodPrivilegedOperations ops)72     public static void put(IBinder token, InputMethodPrivilegedOperations ops) {
73         synchronized (sLock) {
74             if (sRegistry == null) {
75                 sRegistry = new WeakHashMap<>();
76             }
77             final WeakReference<InputMethodPrivilegedOperations> previousOps =
78                     sRegistry.put(token, new WeakReference<>(ops));
79             if (previousOps != null) {
80                 throw new IllegalStateException(previousOps.get() + " is already registered for "
81                         + " this token=" + token + " newOps=" + ops);
82             }
83         }
84     }
85 
86     /**
87      * Get a {@link InputMethodPrivilegedOperations} from the given IME token.  If it is not
88      * available, return a fake instance that does nothing except for showing warning messages.
89      *
90      * @param token IME token
91      * @return real {@link InputMethodPrivilegedOperations} object if {@code token} is still valid.
92      *         Otherwise a fake instance of {@link InputMethodPrivilegedOperations} hat does nothing
93      *         except for showing warning messages
94      */
95     @NonNull
96     @AnyThread
get(IBinder token)97     public static InputMethodPrivilegedOperations get(IBinder token) {
98         synchronized (sLock) {
99             if (sRegistry == null) {
100                 return getNopOps();
101             }
102             final WeakReference<InputMethodPrivilegedOperations> wrapperRef = sRegistry.get(token);
103             if (wrapperRef == null) {
104                 return getNopOps();
105             }
106             final InputMethodPrivilegedOperations wrapper = wrapperRef.get();
107             if (wrapper == null) {
108                 return getNopOps();
109             }
110             return wrapper;
111         }
112     }
113 
114     /**
115      * Explicitly removes the specified entry.
116      *
117      * <p>Note: Calling this method is optional. In general, {@link WeakHashMap} and
118      * {@link WeakReference} guarantee that the entry will be removed after specified binder proxies
119      * are garbage collected.</p>
120      *
121      * @param token IME token to be removed.
122      */
123     @AnyThread
remove(IBinder token)124     public static void remove(IBinder token) {
125         synchronized (sLock) {
126             if (sRegistry == null) {
127                 return;
128             }
129             sRegistry.remove(token);
130             if (sRegistry.isEmpty()) {
131                 sRegistry = null;
132             }
133         }
134     }
135 
136     /**
137      * Check the given IME token registration status.
138      *
139      * @param token IME token
140      * @return {@code true} when the IME token has already registered
141      *         {@link InputMethodPrivilegedOperations}, {@code false} otherwise.
142      */
143     @AnyThread
isRegistered(IBinder token)144     public static boolean isRegistered(IBinder token) {
145         synchronized (sLock) {
146             if (sRegistry == null) {
147                 return false;
148             }
149             return sRegistry.containsKey(token);
150         }
151     }
152 }
153