diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2017-03-31 20:09:34 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2017-04-04 12:33:59 -0700 |
commit | ca18746163621211847a2f184d19a6b3e2b4a1c0 (patch) | |
tree | 4dcebc131b550ef51b79248a14fc0c39afff0d8d /src/com/android/launcher3/graphics/IconShapeOverride.java | |
parent | 2f54a1625ae031ffbb2a99a00c7b25de3c677828 (diff) | |
download | android_packages_apps_Trebuchet-ca18746163621211847a2f184d19a6b3e2b4a1c0.tar.gz android_packages_apps_Trebuchet-ca18746163621211847a2f184d19a6b3e2b4a1c0.tar.bz2 android_packages_apps_Trebuchet-ca18746163621211847a2f184d19a6b3e2b4a1c0.zip |
Adding support for dynamically change icon shape for AdaptiveIcons
> This would allow developers to verify their icon designs on different
device configurations
> This settings is only visible when developer settings is enabled
Change-Id: I7e32abfede001c134f23390734dcd39c93b68b9a
Diffstat (limited to 'src/com/android/launcher3/graphics/IconShapeOverride.java')
-rw-r--r-- | src/com/android/launcher3/graphics/IconShapeOverride.java | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/src/com/android/launcher3/graphics/IconShapeOverride.java b/src/com/android/launcher3/graphics/IconShapeOverride.java new file mode 100644 index 000000000..6e4d36642 --- /dev/null +++ b/src/com/android/launcher3/graphics/IconShapeOverride.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.graphics; + +import android.annotation.TargetApi; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Build; +import android.os.SystemClock; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; + +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherFiles; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.util.LooperExecuter; + +import java.lang.reflect.Field; + +/** + * Utility class to override shape of {@link android.graphics.drawable.AdaptiveIconDrawable}. + */ +@TargetApi(Build.VERSION_CODES.O) +public class IconShapeOverride { + + private static final String TAG = "IconShapeOverride"; + + public static final String KEY_PREFERENCE = "pref_override_icon_shape"; + + // Time to wait before killing the process this ensures that the progress bar is visible for + // sufficient time so that there is no flicker. + private static final long PROCESS_KILL_DELAY_MS = 1000; + + private static final int RESTART_REQUEST_CODE = 42; // the answer to everything + + public static boolean isSupported(Context context) { + if (!Utilities.isAtLeastO()) { + return false; + } + // Only supported when developer settings is enabled + if (Settings.Global.getInt(context.getContentResolver(), + Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 1) { + return false; + } + + try { + if (getSystemResField().get(null) != Resources.getSystem()) { + // Our assumption that mSystem is the system resource is not true. + return false; + } + } catch (Exception e) { + // Ignore, not supported + return false; + } + + return getConfigResId() != 0; + } + + public static void apply(Context context) { + if (!Utilities.isAtLeastO()) { + return; + } + String path = getAppliedValue(context); + if (TextUtils.isEmpty(path)) { + return; + } + if (!isSupported(context)) { + return; + } + + // magic + try { + Resources override = + new ResourcesOverride(Resources.getSystem(), getConfigResId(), path); + getSystemResField().set(null, override); + } catch (Exception e) { + Log.e(TAG, "Unable to override icon shape", e); + // revert value. + prefs(context).edit().remove(KEY_PREFERENCE).apply(); + } + } + + private static Field getSystemResField() throws Exception { + Field staticField = Resources.class.getDeclaredField("mSystem"); + staticField.setAccessible(true); + return staticField; + } + + private static int getConfigResId() { + return Resources.getSystem().getIdentifier("config_icon_mask", "string", "android"); + } + + private static String getAppliedValue(Context context) { + return prefs(context).getString(KEY_PREFERENCE, ""); + } + + private static SharedPreferences prefs(Context context) { + return context.getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, 0); + } + + public static void handlePreferenceUi(ListPreference preference) { + Context context = preference.getContext(); + preference.setValue(getAppliedValue(context)); + preference.setOnPreferenceChangeListener(new PreferenceChangeHandler(context)); + } + + private static class ResourcesOverride extends Resources { + + private final int mOverrideId; + private final String mOverrideValue; + + @SuppressWarnings("deprecated") + public ResourcesOverride(Resources parent, int overrideId, String overrideValue) { + super(parent.getAssets(), parent.getDisplayMetrics(), parent.getConfiguration()); + mOverrideId = overrideId; + mOverrideValue = overrideValue; + } + + @NonNull + @Override + public String getString(int id) throws NotFoundException { + if (id == mOverrideId) { + return mOverrideValue; + } + return super.getString(id); + } + } + + private static class PreferenceChangeHandler implements OnPreferenceChangeListener { + + private final Context mContext; + + private PreferenceChangeHandler(Context context) { + mContext = context; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object o) { + String newValue = (String) o; + if (!getAppliedValue(mContext).equals(newValue)) { + // Value has changed + ProgressDialog.show(mContext, + null /* title */, + mContext.getString(R.string.icon_shape_override_progress), + true /* indeterminate */, + false /* cancelable */); + new LooperExecuter(LauncherModel.getWorkerLooper()).execute( + new OverrideApplyHandler(mContext, newValue)); + } + return false; + } + } + + private static class OverrideApplyHandler implements Runnable { + + private final Context mContext; + private final String mValue; + + private OverrideApplyHandler(Context context, String value) { + mContext = context; + mValue = value; + } + + @Override + public void run() { + // Synchronously write the preference. + prefs(mContext).edit().putString(KEY_PREFERENCE, mValue).commit(); + // Clear the icon cache. + LauncherAppState.getInstance(mContext).getIconCache().clear(); + + // Wait for it + try { + Thread.sleep(PROCESS_KILL_DELAY_MS); + } catch (Exception e) { + Log.e(TAG, "Error waiting", e); + } + + // Schedule an alarm before we kill ourself. + Intent homeIntent = new Intent(Intent.ACTION_MAIN) + .addCategory(Intent.CATEGORY_HOME) + .setPackage(mContext.getPackageName()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + PendingIntent pi = PendingIntent.getActivity(mContext, RESTART_REQUEST_CODE, + homeIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); + mContext.getSystemService(AlarmManager.class).setExact( + AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + 50, pi); + + // Kill process + android.os.Process.killProcess(android.os.Process.myPid()); + } + } +} |