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.example.android.multiclientinputmethod;
18 
19 import android.annotation.NonNull;
20 import android.app.Service;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.hardware.display.DisplayManager;
24 import android.hardware.display.DisplayManager.DisplayListener;
25 import android.inputmethodservice.MultiClientInputMethodServiceDelegate;
26 import android.os.IBinder;
27 import android.util.Log;
28 import android.util.SparseIntArray;
29 import android.view.Display;
30 
31 /**
32  * A {@link Service} that implements multi-client IME protocol.
33  */
34 public final class MultiClientInputMethod extends Service implements DisplayListener {
35     private static final String TAG = "MultiClientInputMethod";
36     private static final boolean DEBUG = false;
37 
38     // last client that had active InputConnection for a given displayId.
39     final SparseIntArray mDisplayToLastClientId = new SparseIntArray();
40     // Mapping table from the display where IME is attached to the display where IME window will be
41     // shown.  Assumes that missing display will use the same display for the IME window.
42     SparseIntArray mInputDisplayToImeDisplay;
43     SoftInputWindowManager mSoftInputWindowManager;
44     MultiClientInputMethodServiceDelegate mDelegate;
45 
46     private DisplayManager mDisplayManager;
47 
48     @Override
onCreate()49     public void onCreate() {
50         if (DEBUG) {
51             Log.v(TAG, "onCreate");
52         }
53         mInputDisplayToImeDisplay = buildInputDisplayToImeDisplay();
54         mDelegate = MultiClientInputMethodServiceDelegate.create(this,
55                 new MultiClientInputMethodServiceDelegate.ServiceCallback() {
56                     @Override
57                     public void initialized() {
58                         if (DEBUG) {
59                             Log.i(TAG, "initialized");
60                         }
61                     }
62 
63                     @Override
64                     public void addClient(int clientId, int uid, int pid,
65                             int selfReportedDisplayId) {
66                         int imeDisplayId = mInputDisplayToImeDisplay.get(selfReportedDisplayId,
67                                 selfReportedDisplayId);
68                         final ClientCallbackImpl callback = new ClientCallbackImpl(
69                                 MultiClientInputMethod.this, mDelegate,
70                                 mSoftInputWindowManager, clientId, uid, pid, imeDisplayId);
71                         if (DEBUG) {
72                             Log.v(TAG, "addClient clientId=" + clientId + " uid=" + uid
73                                     + " pid=" + pid + " displayId=" + selfReportedDisplayId
74                                     + " imeDisplayId=" + imeDisplayId);
75                         }
76 
77                         mDelegate.acceptClient(clientId, callback, callback.getDispatcherState(),
78                                 callback.getLooper());
79                     }
80 
81                     @Override
82                     public void removeClient(int clientId) {
83                         if (DEBUG) {
84                             Log.v(TAG, "removeClient clientId=" + clientId);
85                         }
86                     }
87                 });
88         mSoftInputWindowManager = new SoftInputWindowManager(this, mDelegate);
89     }
90 
91     @Override
onDisplayAdded(int displayId)92     public void onDisplayAdded(int displayId) {
93         mInputDisplayToImeDisplay = buildInputDisplayToImeDisplay();
94     }
95 
96     @Override
onDisplayRemoved(int displayId)97     public void onDisplayRemoved(int displayId) {
98         mDisplayToLastClientId.delete(displayId);
99     }
100 
101     @Override
onDisplayChanged(int displayId)102     public void onDisplayChanged(int displayId) {
103     }
104 
105     @Override
onBind(Intent intent)106     public IBinder onBind(Intent intent) {
107         if (DEBUG) {
108             Log.v(TAG, "onBind intent=" + intent);
109         }
110         mDisplayManager = getApplicationContext().getSystemService(DisplayManager.class);
111         mDisplayManager.registerDisplayListener(this, getMainThreadHandler());
112         return mDelegate.onBind(intent);
113     }
114 
115     @Override
onUnbind(Intent intent)116     public boolean onUnbind(Intent intent) {
117         if (DEBUG) {
118             Log.v(TAG, "onUnbind intent=" + intent);
119         }
120         if (mDisplayManager != null) {
121             mDisplayManager.unregisterDisplayListener(this);
122         }
123         return mDelegate.onUnbind(intent);
124     }
125 
126     @Override
onDestroy()127     public void onDestroy() {
128         if (DEBUG) {
129             Log.v(TAG, "onDestroy");
130         }
131         mDelegate.onDestroy();
132     }
133 
134     @NonNull
buildInputDisplayToImeDisplay()135     private SparseIntArray buildInputDisplayToImeDisplay() {
136         Context context = getApplicationContext();
137         String config[] = context.getResources().getStringArray(
138                 R.array.config_inputDisplayToImeDisplay);
139 
140         SparseIntArray inputDisplayToImeDisplay = new SparseIntArray();
141         Display[] displays = context.getSystemService(DisplayManager.class).getDisplays();
142         for (String item: config) {
143             String[] pair = item.split("/");
144             if (pair.length != 2) {
145                 Log.w(TAG, "Skip illegal config: " + item);
146                 continue;
147             }
148             int inputDisplay = findDisplayId(displays, pair[0]);
149             int imeDisplay = findDisplayId(displays, pair[1]);
150             if (inputDisplay != Display.INVALID_DISPLAY && imeDisplay != Display.INVALID_DISPLAY) {
151                 inputDisplayToImeDisplay.put(inputDisplay, imeDisplay);
152             }
153         }
154         return inputDisplayToImeDisplay;
155     }
156 
findDisplayId(Display displays[], String regexp)157     private static int findDisplayId(Display displays[], String regexp) {
158         for (Display display: displays) {
159             if (display.getUniqueId().matches(regexp)) {
160                 int displayId = display.getDisplayId();
161                 if (DEBUG) {
162                     Log.v(TAG, regexp + " matches displayId=" + displayId);
163                 }
164                 return displayId;
165             }
166         }
167         Log.w(TAG, "Can't find the display of " + regexp);
168         return Display.INVALID_DISPLAY;
169     }
170 }
171