1 /*
2  * Copyright (C) 2014 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.inputmethod.keyboard;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.os.Build;
22 import android.os.Build.VERSION_CODES;
23 import android.preference.PreferenceManager;
24 import android.util.Log;
25 
26 import com.android.inputmethod.compat.BuildCompatUtils;
27 import com.android.inputmethod.latin.R;
28 
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 
32 public final class KeyboardTheme implements Comparable<KeyboardTheme> {
33     private static final String TAG = KeyboardTheme.class.getSimpleName();
34 
35     static final String KLP_KEYBOARD_THEME_KEY = "pref_keyboard_layout_20110916";
36     static final String LXX_KEYBOARD_THEME_KEY = "pref_keyboard_theme_20140509";
37 
38     // These should be aligned with Keyboard.themeId and Keyboard.Case.keyboardTheme
39     // attributes' values in attrs.xml.
40     public static final int THEME_ID_ICS = 0;
41     public static final int THEME_ID_KLP = 2;
42     public static final int THEME_ID_LXX_LIGHT = 3;
43     public static final int THEME_ID_LXX_DARK = 4;
44     public static final int DEFAULT_THEME_ID = THEME_ID_KLP;
45 
46     private static KeyboardTheme[] AVAILABLE_KEYBOARD_THEMES;
47 
48     /* package private for testing */
49     static final KeyboardTheme[] KEYBOARD_THEMES = {
50         new KeyboardTheme(THEME_ID_ICS, "ICS", R.style.KeyboardTheme_ICS,
51                 // This has never been selected because we support ICS or later.
52                 VERSION_CODES.BASE),
53         new KeyboardTheme(THEME_ID_KLP, "KLP", R.style.KeyboardTheme_KLP,
54                 // Default theme for ICS, JB, and KLP.
55                 VERSION_CODES.ICE_CREAM_SANDWICH),
56         new KeyboardTheme(THEME_ID_LXX_LIGHT, "LXXLight", R.style.KeyboardTheme_LXX_Light,
57                 // Default theme for LXX.
58                 Build.VERSION_CODES.LOLLIPOP),
59         new KeyboardTheme(THEME_ID_LXX_DARK, "LXXDark", R.style.KeyboardTheme_LXX_Dark,
60                 // This has never been selected as default theme.
61                 VERSION_CODES.BASE),
62     };
63 
64     static {
65         // Sort {@link #KEYBOARD_THEME} by descending order of {@link #mMinApiVersion}.
66         Arrays.sort(KEYBOARD_THEMES);
67     }
68 
69     public final int mThemeId;
70     public final int mStyleId;
71     public final String mThemeName;
72     public final int mMinApiVersion;
73 
74     // Note: The themeId should be aligned with "themeId" attribute of Keyboard style
75     // in values/themes-<style>.xml.
KeyboardTheme(final int themeId, final String themeName, final int styleId, final int minApiVersion)76     private KeyboardTheme(final int themeId, final String themeName, final int styleId,
77             final int minApiVersion) {
78         mThemeId = themeId;
79         mThemeName = themeName;
80         mStyleId = styleId;
81         mMinApiVersion = minApiVersion;
82     }
83 
84     @Override
compareTo(final KeyboardTheme rhs)85     public int compareTo(final KeyboardTheme rhs) {
86         if (mMinApiVersion > rhs.mMinApiVersion) return -1;
87         if (mMinApiVersion < rhs.mMinApiVersion) return 1;
88         return 0;
89     }
90 
91     @Override
equals(final Object o)92     public boolean equals(final Object o) {
93         if (o == this) return true;
94         return (o instanceof KeyboardTheme) && ((KeyboardTheme)o).mThemeId == mThemeId;
95     }
96 
97     @Override
hashCode()98     public int hashCode() {
99         return mThemeId;
100     }
101 
102     /* package private for testing */
searchKeyboardThemeById(final int themeId, final KeyboardTheme[] availableThemeIds)103     static KeyboardTheme searchKeyboardThemeById(final int themeId,
104             final KeyboardTheme[] availableThemeIds) {
105         // TODO: This search algorithm isn't optimal if there are many themes.
106         for (final KeyboardTheme theme : availableThemeIds) {
107             if (theme.mThemeId == themeId) {
108                 return theme;
109             }
110         }
111         return null;
112     }
113 
114     /* package private for testing */
getDefaultKeyboardTheme(final SharedPreferences prefs, final int sdkVersion, final KeyboardTheme[] availableThemeArray)115     static KeyboardTheme getDefaultKeyboardTheme(final SharedPreferences prefs,
116             final int sdkVersion, final KeyboardTheme[] availableThemeArray) {
117         final String klpThemeIdString = prefs.getString(KLP_KEYBOARD_THEME_KEY, null);
118         if (klpThemeIdString != null) {
119             if (sdkVersion <= VERSION_CODES.KITKAT) {
120                 try {
121                     final int themeId = Integer.parseInt(klpThemeIdString);
122                     final KeyboardTheme theme = searchKeyboardThemeById(themeId,
123                             availableThemeArray);
124                     if (theme != null) {
125                         return theme;
126                     }
127                     Log.w(TAG, "Unknown keyboard theme in KLP preference: " + klpThemeIdString);
128                 } catch (final NumberFormatException e) {
129                     Log.w(TAG, "Illegal keyboard theme in KLP preference: " + klpThemeIdString, e);
130                 }
131             }
132             // Remove old preference.
133             Log.i(TAG, "Remove KLP keyboard theme preference: " + klpThemeIdString);
134             prefs.edit().remove(KLP_KEYBOARD_THEME_KEY).apply();
135         }
136         // TODO: This search algorithm isn't optimal if there are many themes.
137         for (final KeyboardTheme theme : availableThemeArray) {
138             if (sdkVersion >= theme.mMinApiVersion) {
139                 return theme;
140             }
141         }
142         return searchKeyboardThemeById(DEFAULT_THEME_ID, availableThemeArray);
143     }
144 
getKeyboardThemeName(final int themeId)145     public static String getKeyboardThemeName(final int themeId) {
146         final KeyboardTheme theme = searchKeyboardThemeById(themeId, KEYBOARD_THEMES);
147         return theme.mThemeName;
148     }
149 
saveKeyboardThemeId(final int themeId, final SharedPreferences prefs)150     public static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs) {
151         saveKeyboardThemeId(themeId, prefs, BuildCompatUtils.EFFECTIVE_SDK_INT);
152     }
153 
154     /* package private for testing */
getPreferenceKey(final int sdkVersion)155     static String getPreferenceKey(final int sdkVersion) {
156         if (sdkVersion <= VERSION_CODES.KITKAT) {
157             return KLP_KEYBOARD_THEME_KEY;
158         }
159         return LXX_KEYBOARD_THEME_KEY;
160     }
161 
162     /* package private for testing */
saveKeyboardThemeId(final int themeId, final SharedPreferences prefs, final int sdkVersion)163     static void saveKeyboardThemeId(final int themeId, final SharedPreferences prefs,
164             final int sdkVersion) {
165         final String prefKey = getPreferenceKey(sdkVersion);
166         prefs.edit().putString(prefKey, Integer.toString(themeId)).apply();
167     }
168 
getKeyboardTheme(final Context context)169     public static KeyboardTheme getKeyboardTheme(final Context context) {
170         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
171         final KeyboardTheme[] availableThemeArray = getAvailableThemeArray(context);
172         return getKeyboardTheme(prefs, BuildCompatUtils.EFFECTIVE_SDK_INT, availableThemeArray);
173     }
174 
175     /* package private for testing */
getAvailableThemeArray(final Context context)176     static KeyboardTheme[] getAvailableThemeArray(final Context context) {
177         if (AVAILABLE_KEYBOARD_THEMES == null) {
178             final int[] availableThemeIdStringArray = context.getResources().getIntArray(
179                     R.array.keyboard_theme_ids);
180             final ArrayList<KeyboardTheme> availableThemeList = new ArrayList<>();
181             for (final int id : availableThemeIdStringArray) {
182                 final KeyboardTheme theme = searchKeyboardThemeById(id, KEYBOARD_THEMES);
183                 if (theme != null) {
184                     availableThemeList.add(theme);
185                 }
186             }
187             AVAILABLE_KEYBOARD_THEMES = availableThemeList.toArray(
188                     new KeyboardTheme[availableThemeList.size()]);
189             Arrays.sort(AVAILABLE_KEYBOARD_THEMES);
190         }
191         return AVAILABLE_KEYBOARD_THEMES;
192     }
193 
194     /* package private for testing */
getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion, final KeyboardTheme[] availableThemeArray)195     static KeyboardTheme getKeyboardTheme(final SharedPreferences prefs, final int sdkVersion,
196             final KeyboardTheme[] availableThemeArray) {
197         final String lxxThemeIdString = prefs.getString(LXX_KEYBOARD_THEME_KEY, null);
198         if (lxxThemeIdString == null) {
199             return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray);
200         }
201         try {
202             final int themeId = Integer.parseInt(lxxThemeIdString);
203             final KeyboardTheme theme = searchKeyboardThemeById(themeId, availableThemeArray);
204             if (theme != null) {
205                 return theme;
206             }
207             Log.w(TAG, "Unknown keyboard theme in LXX preference: " + lxxThemeIdString);
208         } catch (final NumberFormatException e) {
209             Log.w(TAG, "Illegal keyboard theme in LXX preference: " + lxxThemeIdString, e);
210         }
211         // Remove preference that contains unknown or illegal theme id.
212         prefs.edit().remove(LXX_KEYBOARD_THEME_KEY).apply();
213         return getDefaultKeyboardTheme(prefs, sdkVersion, availableThemeArray);
214     }
215 }
216