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 static com.android.launcher3.Utilities.getDevicePrefs; 19 20 import android.annotation.TargetApi; 21 import android.app.AlarmManager; 22 import android.app.PendingIntent; 23 import android.app.ProgressDialog; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.res.Resources; 27 import android.os.Build; 28 import android.os.SystemClock; 29 import android.preference.ListPreference; 30 import android.preference.Preference; 31 import android.preference.Preference.OnPreferenceChangeListener; 32 import android.provider.Settings; 33 import android.support.annotation.NonNull; 34 import android.text.TextUtils; 35 import android.util.Log; 36 37 import com.android.launcher3.LauncherAppState; 38 import com.android.launcher3.LauncherModel; 39 import com.android.launcher3.R; 40 import com.android.launcher3.Utilities; 41 import com.android.launcher3.util.LooperExecutor; 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.ATLEAST_OREO) { 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.ATLEAST_OREO) { 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 getDevicePrefs(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 getDevicePrefs(context).getString(KEY_PREFERENCE, ""); 120 } 121 handlePreferenceUi(ListPreference preference)122 public static void handlePreferenceUi(ListPreference preference) { 123 Context context = preference.getContext(); 124 preference.setValue(getAppliedValue(context)); 125 preference.setOnPreferenceChangeListener(new PreferenceChangeHandler(context)); 126 } 127 128 private static class ResourcesOverride extends Resources { 129 130 private final int mOverrideId; 131 private final String mOverrideValue; 132 133 @SuppressWarnings("deprecation") ResourcesOverride(Resources parent, int overrideId, String overrideValue)134 public ResourcesOverride(Resources parent, int overrideId, String overrideValue) { 135 super(parent.getAssets(), parent.getDisplayMetrics(), parent.getConfiguration()); 136 mOverrideId = overrideId; 137 mOverrideValue = overrideValue; 138 } 139 140 @NonNull 141 @Override getString(int id)142 public String getString(int id) throws NotFoundException { 143 if (id == mOverrideId) { 144 return mOverrideValue; 145 } 146 return super.getString(id); 147 } 148 } 149 150 private static class PreferenceChangeHandler implements OnPreferenceChangeListener { 151 152 private final Context mContext; 153 PreferenceChangeHandler(Context context)154 private PreferenceChangeHandler(Context context) { 155 mContext = context; 156 } 157 158 @Override onPreferenceChange(Preference preference, Object o)159 public boolean onPreferenceChange(Preference preference, Object o) { 160 String newValue = (String) o; 161 if (!getAppliedValue(mContext).equals(newValue)) { 162 // Value has changed 163 ProgressDialog.show(mContext, 164 null /* title */, 165 mContext.getString(R.string.icon_shape_override_progress), 166 true /* indeterminate */, 167 false /* cancelable */); 168 new LooperExecutor(LauncherModel.getWorkerLooper()).execute( 169 new OverrideApplyHandler(mContext, newValue)); 170 } 171 return false; 172 } 173 } 174 175 private static class OverrideApplyHandler implements Runnable { 176 177 private final Context mContext; 178 private final String mValue; 179 OverrideApplyHandler(Context context, String value)180 private OverrideApplyHandler(Context context, String value) { 181 mContext = context; 182 mValue = value; 183 } 184 185 @Override run()186 public void run() { 187 // Synchronously write the preference. 188 getDevicePrefs(mContext).edit().putString(KEY_PREFERENCE, mValue).commit(); 189 // Clear the icon cache. 190 LauncherAppState.getInstance(mContext).getIconCache().clear(); 191 192 // Wait for it 193 try { 194 Thread.sleep(PROCESS_KILL_DELAY_MS); 195 } catch (Exception e) { 196 Log.e(TAG, "Error waiting", e); 197 } 198 199 // Schedule an alarm before we kill ourself. 200 Intent homeIntent = new Intent(Intent.ACTION_MAIN) 201 .addCategory(Intent.CATEGORY_HOME) 202 .setPackage(mContext.getPackageName()) 203 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 204 PendingIntent pi = PendingIntent.getActivity(mContext, RESTART_REQUEST_CODE, 205 homeIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); 206 mContext.getSystemService(AlarmManager.class).setExact( 207 AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 50, pi); 208 209 // Kill process 210 android.os.Process.killProcess(android.os.Process.myPid()); 211 } 212 } 213 } 214