代码视图分析

  1 /*
  2  * Copyright (C) 2013 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.content.Context;
 20 import android.content.pm.PackageManager;
 21 import android.text.TextUtils;
 22 import android.util.Log;
 23 import android.util.Printer;
 24 import android.util.Slog;
 25 import android.view.inputmethod.InputMethodInfo;
 26 import android.view.inputmethod.InputMethodSubtype;
 27 
 28 import com.android.internal.annotations.VisibleForTesting;
 29 import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
 30 
 31 import java.util.ArrayList;
 32 import java.util.Collections;
 33 import java.util.Comparator;
 34 import java.util.HashMap;
 35 import java.util.HashSet;
 36 import java.util.List;
 37 import java.util.Locale;
 38 import java.util.Objects;
 39 import java.util.TreeMap;
 40 
 41 /**
 42  * InputMethodSubtypeSwitchingController controls the switching behavior of the subtypes.
 43  * <p>
 44  * This class is designed to be used from and only from {@link InputMethodManagerService} by using
 45  * {@link InputMethodManagerService#mMethodMap} as a global lock.
 46  * </p>
 47  */
 48 public class InputMethodSubtypeSwitchingController {
 49     private static final String TAG = InputMethodSubtypeSwitchingController.class.getSimpleName();
 50     private static final boolean DEBUG = false;
 51     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
 52 
 53     public static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> {
 54         public final CharSequence mImeName;
 55         public final CharSequence mSubtypeName;
 56         public final InputMethodInfo mImi;
 57         public final int mSubtypeId;
 58         public final boolean mIsSystemLocale;
 59         public final boolean mIsSystemLanguage;
 60 
 61         public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
 62                 InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) {
 63             mImeName = imeName;
 64             mSubtypeName = subtypeName;
 65             mImi = imi;
 66             mSubtypeId = subtypeId;
 67             if (TextUtils.isEmpty(subtypeLocale)) {
 68                 mIsSystemLocale = false;
 69                 mIsSystemLanguage = false;
 70             } else {
 71                 mIsSystemLocale = subtypeLocale.equals(systemLocale);
 72                 if (mIsSystemLocale) {
 73                     mIsSystemLanguage = true;
 74                 } else {
 75                     // TODO: Use Locale#getLanguage or Locale#toLanguageTag
 76                     final String systemLanguage = parseLanguageFromLocaleString(systemLocale);
 77                     final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
 78                     mIsSystemLanguage = systemLanguage.length() >= 2 &&
 79                             systemLanguage.equals(subtypeLanguage);
 80                 }
 81             }
 82         }
 83 
 84         /**
 85          * Returns the language component of a given locale string.
 86          * TODO: Use {@link Locale#getLanguage()} instead.
 87          */
 88         private static String parseLanguageFromLocaleString(final String locale) {
 89             final int idx = locale.indexOf('_');
 90             if (idx < 0) {
 91                 return locale;
 92             } else {
 93                 return locale.substring(0, idx);
 94             }
 95         }
 96 
 97         @Override
 98         public int compareTo(ImeSubtypeListItem other) {
 99             if (TextUtils.isEmpty(mImeName)) {
100                 return 1;
101             }
102             if (TextUtils.isEmpty(other.mImeName)) {
103                 return -1;
104             }
105             if (!TextUtils.equals(mImeName, other.mImeName)) {
106                 return mImeName.toString().compareTo(other.mImeName.toString());
107             }
108             if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
109                 return 0;
110             }
111             if (mIsSystemLocale) {
112                 return -1;
113             }
114             if (other.mIsSystemLocale) {
115                 return 1;
116             }
117             if (mIsSystemLanguage) {
118                 return -1;
119             }
120             if (other.mIsSystemLanguage) {
121                 return 1;
122             }
123             if (TextUtils.isEmpty(mSubtypeName)) {
124                 return 1;
125             }
126             if (TextUtils.isEmpty(other.mSubtypeName)) {
127                 return -1;
128             }
129             return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
130         }
131 
132         @Override
133         public String toString() {
134             return "ImeSubtypeListItem{"
135                     + "mImeName=" + mImeName
136                     + " mSubtypeName=" + mSubtypeName
137                     + " mSubtypeId=" + mSubtypeId
138                     + " mIsSystemLocale=" + mIsSystemLocale
139                     + " mIsSystemLanguage=" + mIsSystemLanguage
140                     + "}";
141         }
142 
143         @Override
144         public boolean equals(Object o) {
145             if (o == this) {
146                 return true;
147             }
148             if (o instanceof ImeSubtypeListItem) {
149                 final ImeSubtypeListItem that = (ImeSubtypeListItem)o;
150                 if (!Objects.equals(this.mImi, that.mImi)) {
151                     return false;
152                 }
153                 if (this.mSubtypeId != that.mSubtypeId) {
154                     return false;
155                 }
156                 return true;
157             }
158             return false;
159         }
160     }
161 
162     private static class InputMethodAndSubtypeList {
163         private final Context mContext;
164         // Used to load label
165         private final PackageManager mPm;
166         private final String mSystemLocaleStr;
167         private final InputMethodSettings mSettings;
168 
169         public InputMethodAndSubtypeList(Context context, InputMethodSettings settings) {
170             mContext = context;
171             mSettings = settings;
172             mPm = context.getPackageManager();
173             final Locale locale = context.getResources().getConfiguration().locale;
174             mSystemLocaleStr = locale != null ? locale.toString() : "";
175         }
176 
177         private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
178                 new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
179                         new Comparator<InputMethodInfo>() {
180                             @Override
181                             public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
182                                 if (imi2 == null)
183                                     return 0;
184                                 if (imi1 == null)
185                                     return 1;
186                                 if (mPm == null) {
187                                     return imi1.getId().compareTo(imi2.getId());
188                                 }
189                                 CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId();
190                                 CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId();
191                                 return imiId1.toString().compareTo(imiId2.toString());
192                             }
193                         });
194 
195         public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() {
196             return getSortedInputMethodAndSubtypeList(true, false, false);
197         }
198 
199         public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
200                 boolean showSubtypes, boolean includeAuxiliarySubtypes, boolean isScreenLocked) {
201             final ArrayList<ImeSubtypeListItem> imList =
202                     new ArrayList<ImeSubtypeListItem>();
203             final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
204                     mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
205                             mContext);
206             if (immis == null || immis.size() == 0) {
207                 return Collections.emptyList();
208             }
209             if (isScreenLocked && includeAuxiliarySubtypes) {
210                 if (DEBUG) {
211                     Slog.w(TAG, "Auxiliary subtypes are not allowed to be shown in lock screen.");
212                 }
213                 includeAuxiliarySubtypes = false;
214             }
215             mSortedImmis.clear();
216             mSortedImmis.putAll(immis);
217             for (InputMethodInfo imi : mSortedImmis.keySet()) {
218                 if (imi == null) {
219                     continue;
220                 }
221                 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
222                 HashSet<String> enabledSubtypeSet = new HashSet<String>();
223                 for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
224                     enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
225                 }
226                 final CharSequence imeLabel = imi.loadLabel(mPm);
227                 if (showSubtypes && enabledSubtypeSet.size() > 0) {
228                     final int subtypeCount = imi.getSubtypeCount();
229                     if (DEBUG) {
230                         Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
231                     }
232                     for (int j = 0; j < subtypeCount; ++j) {
233                         final InputMethodSubtype subtype = imi.getSubtypeAt(j);
234                         final String subtypeHashCode = String.valueOf(subtype.hashCode());
235                         // We show all enabled IMEs and subtypes when an IME is shown.
236                         if (enabledSubtypeSet.contains(subtypeHashCode)
237                                 && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) {
238                             final CharSequence subtypeLabel =
239                                     subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
240                                             .getDisplayName(mContext, imi.getPackageName(),
241                                                     imi.getServiceInfo().applicationInfo);
242                             imList.add(new ImeSubtypeListItem(imeLabel,
243                                     subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
244 
245                             // Removing this subtype from enabledSubtypeSet because we no
246                             // longer need to add an entry of this subtype to imList to avoid
247                             // duplicated entries.
248                             enabledSubtypeSet.remove(subtypeHashCode);
249                         }
250                     }
251                 } else {
252                     imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, null,
253                             mSystemLocaleStr));
254                 }
255             }
256             Collections.sort(imList);
257             return imList;
258         }
259     }
260 
261     private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) {
262         return subtype != null ? InputMethodUtils.getSubtypeIdFromHashCode(imi,
263                 subtype.hashCode()) : NOT_A_SUBTYPE_ID;
264     }
265 
266     private static class StaticRotationList {
267         private final List<ImeSubtypeListItem> mImeSubtypeList;
268         public StaticRotationList(final List<ImeSubtypeListItem> imeSubtypeList) {
269             mImeSubtypeList = imeSubtypeList;
270         }
271 
272         /**
273          * Returns the index of the specified input method and subtype in the given list.
274          * @param imi The {@link InputMethodInfo} to be searched.
275          * @param subtype The {@link InputMethodSubtype} to be searched. null if the input method
276          * does not have a subtype.
277          * @return The index in the given list. -1 if not found.
278          */
279         private int getIndex(InputMethodInfo imi, InputMethodSubtype subtype) {
280             final int currentSubtypeId = calculateSubtypeId(imi, subtype);
281             final int N = mImeSubtypeList.size();
282             for (int i = 0; i < N; ++i) {
283                 final ImeSubtypeListItem isli = mImeSubtypeList.get(i);
284                 // Skip until the current IME/subtype is found.
285                 if (imi.equals(isli.mImi) && isli.mSubtypeId == currentSubtypeId) {
286                     return i;
287                 }
288             }
289             return -1;
290         }
291 
292         public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
293                 InputMethodInfo imi, InputMethodSubtype subtype) {
294             if (imi == null) {
295                 return null;
296             }
297             if (mImeSubtypeList.size() <= 1) {
298                 return null;
299             }
300             final int currentIndex = getIndex(imi, subtype);
301             if (currentIndex < 0) {
302                 return null;
303             }
304             final int N = mImeSubtypeList.size();
305             for (int offset = 1; offset < N; ++offset) {
306                 // Start searching the next IME/subtype from the next of the current index.
307                 final int candidateIndex = (currentIndex + offset) % N;
308                 final ImeSubtypeListItem candidate = mImeSubtypeList.get(candidateIndex);
309                 // Skip if searching inside the current IME only, but the candidate is not
310                 // the current IME.
311                 if (onlyCurrentIme && !imi.equals(candidate.mImi)) {
312                     continue;
313                 }
314                 return candidate;
315             }
316             return null;
317         }
318 
319         protected void dump(final Printer pw, final String prefix) {
320             final int N = mImeSubtypeList.size();
321             for (int i = 0; i < N; ++i) {
322                 final int rank = i;
323                 final ImeSubtypeListItem item = mImeSubtypeList.get(i);
324                 pw.println(prefix + "rank=" + rank + " item=" + item);
325             }
326         }
327     }
328 
329     private static class DynamicRotationList {
330         private static final String TAG = DynamicRotationList.class.getSimpleName();
331         private final List<ImeSubtypeListItem> mImeSubtypeList;
332         private final int[] mUsageHistoryOfSubtypeListItemIndex;
333 
334         private DynamicRotationList(final List<ImeSubtypeListItem> imeSubtypeListItems) {
335             mImeSubtypeList = imeSubtypeListItems;
336             mUsageHistoryOfSubtypeListItemIndex = new int[mImeSubtypeList.size()];
337             final int N = mImeSubtypeList.size();
338             for (int i = 0; i < N; i++) {
339                 mUsageHistoryOfSubtypeListItemIndex[i] = i;
340             }
341         }
342 
343         /**
344          * Returns the index of the specified object in
345          * {@link #mUsageHistoryOfSubtypeListItemIndex}.
346          * <p>We call the index of {@link #mUsageHistoryOfSubtypeListItemIndex} as "Usage Rank"
347          * so as not to be confused with the index in {@link #mImeSubtypeList}.
348          * @return -1 when the specified item doesn't belong to {@link #mImeSubtypeList} actually.
349          */
350         private int getUsageRank(final InputMethodInfo imi, InputMethodSubtype subtype) {
351             final int currentSubtypeId = calculateSubtypeId(imi, subtype);
352             final int N = mUsageHistoryOfSubtypeListItemIndex.length;
353             for (int usageRank = 0; usageRank < N; usageRank++) {
354                 final int subtypeListItemIndex = mUsageHistoryOfSubtypeListItemIndex[usageRank];
355                 final ImeSubtypeListItem subtypeListItem =
356                         mImeSubtypeList.get(subtypeListItemIndex);
357                 if (subtypeListItem.mImi.equals(imi) &&
358                         subtypeListItem.mSubtypeId == currentSubtypeId) {
359                     return usageRank;
360                 }
361             }
362             // Not found in the known IME/Subtype list.
363             return -1;
364         }
365 
366         public void onUserAction(InputMethodInfo imi, InputMethodSubtype subtype) {
367             final int currentUsageRank = getUsageRank(imi, subtype);
368             // Do nothing if currentUsageRank == -1 (not found), or currentUsageRank == 0
369             if (currentUsageRank <= 0) {
370                 return;
371             }
372             final int currentItemIndex = mUsageHistoryOfSubtypeListItemIndex[currentUsageRank];
373             System.arraycopy(mUsageHistoryOfSubtypeListItemIndex, 0,
374                     mUsageHistoryOfSubtypeListItemIndex, 1, currentUsageRank);
375             mUsageHistoryOfSubtypeListItemIndex[0] = currentItemIndex;
376         }
377 
378         public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme,
379                 InputMethodInfo imi, InputMethodSubtype subtype) {
380             int currentUsageRank = getUsageRank(imi, subtype);
381             if (currentUsageRank < 0) {
382                 if (DEBUG) {
383                     Slog.d(TAG, "IME/subtype is not found: " + imi.getId() + ", " + subtype);
384                 }
385                 return null;
386             }
387             final int N = mUsageHistoryOfSubtypeListItemIndex.length;
388             for (int i = 1; i < N; i++) {
389                 final int subtypeListItemRank = (currentUsageRank + i) % N;
390                 final int subtypeListItemIndex =
391                         mUsageHistoryOfSubtypeListItemIndex[subtypeListItemRank];
392                 final ImeSubtypeListItem subtypeListItem =
393                         mImeSubtypeList.get(subtypeListItemIndex);
394                 if (onlyCurrentIme && !imi.equals(subtypeListItem.mImi)) {
395                     continue;
396                 }
397                 return subtypeListItem;
398             }
399             return null;
400         }
401 
402         protected void dump(final Printer pw, final String prefix) {
403             for (int i = 0; i < mUsageHistoryOfSubtypeListItemIndex.length; ++i) {
404                 final int rank = mUsageHistoryOfSubtypeListItemIndex[i];
405                 final ImeSubtypeListItem item = mImeSubtypeList.get(i);
406                 pw.println(prefix + "rank=" + rank + " item=" + item);
407             }
408         }
409     }
410 
411     @VisibleForTesting
412     public static class ControllerImpl {
413         private final DynamicRotationList mSwitchingAwareRotationList;
414         private final StaticRotationList mSwitchingUnawareRotationList;
415 
416         public static ControllerImpl createFrom(final ControllerImpl currentInstance,
417                 final List<ImeSubtypeListItem> sortedEnabledItems) {
418             DynamicRotationList switchingAwareRotationList = null;
419             {
420                 final List<ImeSubtypeListItem> switchingAwareImeSubtypes =
421                         filterImeSubtypeList(sortedEnabledItems,
422                                 true /* supportsSwitchingToNextInputMethod */);
423                 if (currentInstance != null &&
424                         currentInstance.mSwitchingAwareRotationList != null &&
425                         Objects.equals(currentInstance.mSwitchingAwareRotationList.mImeSubtypeList,
426                                 switchingAwareImeSubtypes)) {
427                     // Can reuse the current instance.
428                     switchingAwareRotationList = currentInstance.mSwitchingAwareRotationList;
429                 }
430                 if (switchingAwareRotationList == null) {
431                     switchingAwareRotationList = new DynamicRotationList(switchingAwareImeSubtypes);
432                 }
433             }
434 
435             StaticRotationList switchingUnawareRotationList = null;
436             {
437                 final List<ImeSubtypeListItem> switchingUnawareImeSubtypes = filterImeSubtypeList(
438                         sortedEnabledItems, false /* supportsSwitchingToNextInputMethod */);
439                 if (currentInstance != null &&
440                         currentInstance.mSwitchingUnawareRotationList != null &&
441                         Objects.equals(
442                                 currentInstance.mSwitchingUnawareRotationList.mImeSubtypeList,
443                                 switchingUnawareImeSubtypes)) {
444                     // Can reuse the current instance.
445                     switchingUnawareRotationList = currentInstance.mSwitchingUnawareRotationList;
446                 }
447                 if (switchingUnawareRotationList == null) {
448                     switchingUnawareRotationList =
449                             new StaticRotationList(switchingUnawareImeSubtypes);
450                 }
451             }
452 
453             return new ControllerImpl(switchingAwareRotationList, switchingUnawareRotationList);
454         }
455 
456         private ControllerImpl(final DynamicRotationList switchingAwareRotationList,
457                 final StaticRotationList switchingUnawareRotationList) {
458             mSwitchingAwareRotationList = switchingAwareRotationList;
459             mSwitchingUnawareRotationList = switchingUnawareRotationList;
460         }
461 
462         public ImeSubtypeListItem getNextInputMethod(boolean onlyCurrentIme, InputMethodInfo imi,
463                 InputMethodSubtype subtype) {
464             if (imi == null) {
465                 return null;
466             }
467             if (imi.supportsSwitchingToNextInputMethod()) {
468                 return mSwitchingAwareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
469                         subtype);
470             } else {
471                 return mSwitchingUnawareRotationList.getNextInputMethodLocked(onlyCurrentIme, imi,
472                         subtype);
473             }
474         }
475 
476         public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
477             if (imi == null) {
478                 return;
479             }
480             if (imi.supportsSwitchingToNextInputMethod()) {
481                 mSwitchingAwareRotationList.onUserAction(imi, subtype);
482             }
483         }
484 
485         private static List<ImeSubtypeListItem> filterImeSubtypeList(
486                 final List<ImeSubtypeListItem> items,
487                 final boolean supportsSwitchingToNextInputMethod) {
488             final ArrayList<ImeSubtypeListItem> result = new ArrayList<>();
489             final int ALL_ITEMS_COUNT = items.size();
490             for (int i = 0; i < ALL_ITEMS_COUNT; i++) {
491                 final ImeSubtypeListItem item = items.get(i);
492                 if (item.mImi.supportsSwitchingToNextInputMethod() ==
493                         supportsSwitchingToNextInputMethod) {
494                     result.add(item);
495                 }
496             }
497             return result;
498         }
499 
500         protected void dump(final Printer pw) {
501             pw.println("    mSwitchingAwareRotationList:");
502             mSwitchingAwareRotationList.dump(pw, "      ");
503             pw.println("    mSwitchingUnawareRotationList:");
504             mSwitchingUnawareRotationList.dump(pw, "      ");
505         }
506     }
507 
508     private final InputMethodSettings mSettings;
509     private InputMethodAndSubtypeList mSubtypeList;
510     private ControllerImpl mController;
511 
512     private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) {
513         mSettings = settings;
514         resetCircularListLocked(context);
515     }
516 
517     public static InputMethodSubtypeSwitchingController createInstanceLocked(
518             InputMethodSettings settings, Context context) {
519         return new InputMethodSubtypeSwitchingController(settings, context);
520     }
521 
522     public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
523         if (mController == null) {
524             if (DEBUG) {
525                 Log.e(TAG, "mController shouldn't be null.");
526             }
527             return;
528         }
529         mController.onUserActionLocked(imi, subtype);
530     }
531 
532     public void resetCircularListLocked(Context context) {
533         mSubtypeList = new InputMethodAndSubtypeList(context, mSettings);
534         mController = ControllerImpl.createFrom(mController,
535                 mSubtypeList.getSortedInputMethodAndSubtypeList());
536     }
537 
538     public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi,
539             InputMethodSubtype subtype) {
540         if (mController == null) {
541             if (DEBUG) {
542                 Log.e(TAG, "mController shouldn't be null.");
543             }
544             return null;
545         }
546         return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
547     }
548 
549     public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListLocked(boolean showSubtypes,
550             boolean includingAuxiliarySubtypes, boolean isScreenLocked) {
551         return mSubtypeList.getSortedInputMethodAndSubtypeList(
552                 showSubtypes, includingAuxiliarySubtypes, isScreenLocked);
553     }
554 
555     public void dump(final Printer pw) {
556         if (mController != null) {
557             mController.dump(pw);
558         } else {
559             pw.println("    mController=null");
560         }
561     }
562 }

猜你喜欢

转载自www.cnblogs.com/liecen/p/9215455.html