1 /* 2 * Copyright (C) 2017 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 package com.android.launcher3.graphics; 17 18 import android.annotation.TargetApi; 19 import android.app.AlarmManager; 20 import android.app.PendingIntent; 21 import android.app.ProgressDialog; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.SharedPreferences; 25 import android.content.res.Resources; 26 import android.os.Build; 27 import android.os.SystemClock; 28 import android.preference.ListPreference; 29 import android.preference.Preference; 30 import android.preference.Preference.OnPreferenceChangeListener; 31 import android.provider.Settings; 32 import android.support.annotation.NonNull; 33 import android.text.TextUtils; 34 import android.util.Log; 35 36 import com.android.launcher3.LauncherAppState; 37 import com.android.launcher3.LauncherFiles; 38 import com.android.launcher3.LauncherModel; 39 import com.android.launcher3.R; 40 import com.android.launcher3.Utilities; 41 import com.android.launcher3.util.LooperExecuter; 42 43 import java.lang.reflect.Field; 44 45 /** 46 * Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}. 47 */ 48 @TargetApi(Build.VERSION_CODES.O) 49 public class IconShapeOverride { 50 51 private static final String TAG = "IconShapeOverride"; 52 53 public static final String KEY_PREFERENCE = "pref_override_icon_shape"; 54 55 // Time to wait before killing the process this ensures that the progress bar is visible for 56 // sufficient time so that there is no flicker. 57 private static final long PROCESS_KILL_DELAY_MS = 1000; 58 59 private static final int RESTART_REQUEST_CODE = 42; // the answer to everything 60 isSupported(Context context)61 public static boolean isSupported(Context context) { 62 if (!Utilities.isAtLeastO()) { 63 return false; 64 } 65 // Only supported when developer settings is enabled 66 if (Settings.Global.getInt(context.getContentResolver(), 67 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 1) { 68 return false; 69 } 70 71 try { 72 if (getSystemResField().get(null) != Resources.getSystem()) { 73 // Our assumption that mSystem is the system resource is not true. 74 return false; 75 } 76 } catch (Exception e) { 77 // Ignore, not supported 78 return false; 79 } 80 81 return getConfigResId() != 0; 82 } 83 apply(Context context)84 public static void apply(Context context) { 85 if (!Utilities.isAtLeastO()) { 86 return; 87 } 88 String path = getAppliedValue(context); 89 if (TextUtils.isEmpty(path)) { 90 return; 91 } 92 if (!isSupported(context)) { 93 return; 94 } 95 96 // magic 97 try { 98 Resources override = 99 new ResourcesOverride(Resources.getSystem(), getConfigResId(), path); 100 getSystemResField().set(null, override); 101 } catch (Exception e) { 102 Log.e(TAG, "Unable to override icon shape", e); 103 // revert value. 104 prefs(context).edit().remove(KEY_PREFERENCE).apply(); 105 } 106 } 107 getSystemResField()108 private static Field getSystemResField() throws Exception { 109 Field staticField = Resources.class.getDeclaredField("mSystem"); 110 staticField.setAccessible(true); 111 return staticField; 112 } 113 getConfigResId()114 private static int getConfigResId() { 115 return Resources.getSystem().getIdentifier("config_icon_mask", "string", "android"); 116 } 117 getAppliedValue(Context context)118 private static String getAppliedValue(Context context) { 119 return prefs(context).getString(KEY_PREFERENCE, ""); 120 } 121 prefs(Context context)122 private static SharedPreferences prefs(Context context) { 123 return context.getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0); 124 } 125 handlePreferenceUi(ListPreference preference)126 public static void handlePreferenceUi(ListPreference preference) { 127 Context context = preference.getContext(); 128 preference.setValue(getAppliedValue(context)); 129 preference.setOnPreferenceChangeListener(new PreferenceChangeHandler(context)); 130 } 131 132 private static class ResourcesOverride extends Resources { 133 134 private final int mOverrideId; 135 private final String mOverrideValue; 136 137 @SuppressWarnings("deprecated") ResourcesOverride(Resources parent, int overrideId, String overrideValue)138 public ResourcesOverride(Resources parent, int overrideId, String overrideValue) { 139 super(parent.getAssets(), parent.getDisplayMetrics(), parent.getConfiguration()); 140 mOverrideId = overrideId; 141 mOverrideValue = overrideValue; 142 } 143 144 @NonNull 145 @Override getString(int id)146 public String getString(int id) throws NotFoundException { 147 if (id == mOverrideId) { 148 return mOverrideValue; 149 } 150 return super.getString(id); 151 } 152 } 153 154 private static class PreferenceChangeHandler implements OnPreferenceChangeListener { 155 156 private final Context mContext; 157 PreferenceChangeHandler(Context context)158 private PreferenceChangeHandler(Context context) { 159 mContext = context; 160 } 161 162 @Override onPreferenceChange(Preference preference, Object o)163 public boolean onPreferenceChange(Preference preference, Object o) { 164 String newValue = (String) o; 165 if (!getAppliedValue(mContext).equals(newValue)) { 166 // Value has changed 167 ProgressDialog.show(mContext, 168 null /* title */, 169 mContext.getString(R.string.icon_shape_override_progress), 170 true /* indeterminate */, 171 false /* cancelable */); 172 new LooperExecuter(LauncherModel.getWorkerLooper()).execute( 173 new OverrideApplyHandler(mContext, newValue)); 174 } 175 return false; 176 } 177 } 178 179 private static class OverrideApplyHandler implements Runnable { 180 181 private final Context mContext; 182 private final String mValue; 183 OverrideApplyHandler(Context context, String value)184 private OverrideApplyHandler(Context context, String value) { 185 mContext = context; 186 mValue = value; 187 } 188 189 @Override run()190 public void run() { 191 // Synchronously write the preference. 192 prefs(mContext).edit().putString(KEY_PREFERENCE, mValue).commit(); 193 // Clear the icon cache. 194 LauncherAppState.getInstance(mContext).getIconCache().clear(); 195 196 // Wait for it 197 try { 198 Thread.sleep(PROCESS_KILL_DELAY_MS); 199 } catch (Exception e) { 200 Log.e(TAG, "Error waiting", e); 201 } 202 203 // Schedule an alarm before we kill ourself. 204 Intent homeIntent = new Intent(Intent.ACTION_MAIN) 205 .addCategory(Intent.CATEGORY_HOME) 206 .setPackage(mContext.getPackageName()) 207 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 208 PendingIntent pi = PendingIntent.getActivity(mContext, RESTART_REQUEST_CODE, 209 homeIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); 210 mContext.getSystemService(AlarmManager.class).setExact( 211 AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 50, pi); 212 213 // Kill process 214 android.os.Process.killProcess(android.os.Process.myPid()); 215 } 216 } 217 } 218