From bcd90a38b28f7c57bce8057d855e4c276aab2776 Mon Sep 17 00:00:00 2001 From: Clark Scheff Date: Sun, 28 Aug 2016 09:21:05 -0700 Subject: Theme chooser for the people Let freedom ring! Change-Id: I12b3e6b5d46eb2e13afd841dfd5c215af64188d8 TICKET: OSS-67 --- src/android/support/v4/view/ThemeViewPager.java | 18 +- .../widget/ComponentSelectorLinearLayout.java | 16 +- src/com/cyngn/theme/chooser/AppReceiver.java | 59 - src/com/cyngn/theme/chooser/BootReceiver.java | 42 - src/com/cyngn/theme/chooser/ChooserActivity.java | 1191 -------- src/com/cyngn/theme/chooser/ComponentCardView.java | 219 -- src/com/cyngn/theme/chooser/ComponentSelector.java | 920 ------ .../theme/chooser/IconTransitionDrawable.java | 171 -- src/com/cyngn/theme/chooser/MyThemeFragment.java | 693 ----- .../chooser/NewFragmentStatePagerAdapter.java | 332 --- .../chooser/NotificationHijackingService.java | 74 - src/com/cyngn/theme/chooser/PagerContainer.java | 226 -- src/com/cyngn/theme/chooser/ThemeFragment.java | 3045 ------------------- src/com/cyngn/theme/chooser/WallpaperCardView.java | 146 - .../theme/perapptheming/PerAppThemeListLayout.java | 160 - .../theme/perapptheming/PerAppThemeListView.java | 59 - .../theme/perapptheming/PerAppThemingWindow.java | 1064 ------- src/com/cyngn/theme/util/AudioUtils.java | 92 - src/com/cyngn/theme/util/BootAnimationHelper.java | 305 -- src/com/cyngn/theme/util/CursorLoaderHelper.java | 433 --- src/com/cyngn/theme/util/FontConfigParser.java | 161 -- src/com/cyngn/theme/util/IconPreviewHelper.java | 183 -- src/com/cyngn/theme/util/NotificationHelper.java | 82 - src/com/cyngn/theme/util/PreferenceUtils.java | 119 - src/com/cyngn/theme/util/ThemedTypefaceHelper.java | 127 - src/com/cyngn/theme/util/TypefaceHelperCache.java | 43 - src/com/cyngn/theme/util/Utils.java | 710 ----- src/com/cyngn/theme/util/WallpaperUtils.java | 426 --- .../theme/widget/AutoSnapHorizontalScrollView.java | 154 - src/com/cyngn/theme/widget/BootAniImageView.java | 210 -- .../cyngn/theme/widget/ConfirmCancelOverlay.java | 74 - src/com/cyngn/theme/widget/FittedTextView.java | 84 - src/com/cyngn/theme/widget/LatoTextView.java | 170 -- src/com/cyngn/theme/widget/LockableScrollView.java | 55 - src/com/cyngn/theme/widget/NavBarSpace.java | 39 - src/com/cyngn/theme/widget/ThemeTagLayout.java | 125 - .../viewpagerindicator/CirclePageIndicator.java | 2 +- src/org/cyanogenmod/theme/chooser/AppReceiver.java | 73 + .../cyanogenmod/theme/chooser/BootReceiver.java | 56 + .../cyanogenmod/theme/chooser/ChooserActivity.java | 1191 ++++++++ .../theme/chooser/ComponentCardView.java | 233 ++ .../theme/chooser/ComponentSelector.java | 934 ++++++ .../theme/chooser/IconTransitionDrawable.java | 171 ++ .../cyanogenmod/theme/chooser/MyThemeFragment.java | 707 +++++ .../chooser/NewFragmentStatePagerAdapter.java | 333 +++ .../chooser/NotificationHijackingService.java | 88 + .../cyanogenmod/theme/chooser/PagerContainer.java | 219 ++ .../cyanogenmod/theme/chooser/ThemeFragment.java | 3059 ++++++++++++++++++++ .../theme/chooser/WallpaperCardView.java | 160 + .../theme/perapptheming/PerAppThemeListLayout.java | 174 ++ .../theme/perapptheming/PerAppThemeListView.java | 74 + .../theme/perapptheming/PerAppThemingWindow.java | 1078 +++++++ src/org/cyanogenmod/theme/util/AudioUtils.java | 106 + .../theme/util/BootAnimationHelper.java | 319 ++ .../cyanogenmod/theme/util/CursorLoaderHelper.java | 447 +++ .../cyanogenmod/theme/util/FontConfigParser.java | 175 ++ .../cyanogenmod/theme/util/IconPreviewHelper.java | 197 ++ .../cyanogenmod/theme/util/NotificationHelper.java | 96 + .../cyanogenmod/theme/util/PreferenceUtils.java | 133 + .../theme/util/ThemedTypefaceHelper.java | 141 + .../theme/util/TypefaceHelperCache.java | 57 + src/org/cyanogenmod/theme/util/Utils.java | 724 +++++ src/org/cyanogenmod/theme/util/WallpaperUtils.java | 440 +++ .../theme/widget/AutoSnapHorizontalScrollView.java | 168 ++ .../cyanogenmod/theme/widget/BootAniImageView.java | 226 ++ .../theme/widget/ConfirmCancelOverlay.java | 89 + .../cyanogenmod/theme/widget/FittedTextView.java | 98 + src/org/cyanogenmod/theme/widget/LatoTextView.java | 184 ++ .../theme/widget/LockableScrollView.java | 69 + src/org/cyanogenmod/theme/widget/NavBarSpace.java | 53 + .../cyanogenmod/theme/widget/ThemeTagLayout.java | 140 + 71 files changed, 12444 insertions(+), 11997 deletions(-) delete mode 100644 src/com/cyngn/theme/chooser/AppReceiver.java delete mode 100644 src/com/cyngn/theme/chooser/BootReceiver.java delete mode 100644 src/com/cyngn/theme/chooser/ChooserActivity.java delete mode 100644 src/com/cyngn/theme/chooser/ComponentCardView.java delete mode 100644 src/com/cyngn/theme/chooser/ComponentSelector.java delete mode 100644 src/com/cyngn/theme/chooser/IconTransitionDrawable.java delete mode 100644 src/com/cyngn/theme/chooser/MyThemeFragment.java delete mode 100644 src/com/cyngn/theme/chooser/NewFragmentStatePagerAdapter.java delete mode 100644 src/com/cyngn/theme/chooser/NotificationHijackingService.java delete mode 100644 src/com/cyngn/theme/chooser/PagerContainer.java delete mode 100644 src/com/cyngn/theme/chooser/ThemeFragment.java delete mode 100644 src/com/cyngn/theme/chooser/WallpaperCardView.java delete mode 100644 src/com/cyngn/theme/perapptheming/PerAppThemeListLayout.java delete mode 100644 src/com/cyngn/theme/perapptheming/PerAppThemeListView.java delete mode 100644 src/com/cyngn/theme/perapptheming/PerAppThemingWindow.java delete mode 100644 src/com/cyngn/theme/util/AudioUtils.java delete mode 100644 src/com/cyngn/theme/util/BootAnimationHelper.java delete mode 100644 src/com/cyngn/theme/util/CursorLoaderHelper.java delete mode 100644 src/com/cyngn/theme/util/FontConfigParser.java delete mode 100644 src/com/cyngn/theme/util/IconPreviewHelper.java delete mode 100644 src/com/cyngn/theme/util/NotificationHelper.java delete mode 100644 src/com/cyngn/theme/util/PreferenceUtils.java delete mode 100644 src/com/cyngn/theme/util/ThemedTypefaceHelper.java delete mode 100644 src/com/cyngn/theme/util/TypefaceHelperCache.java delete mode 100644 src/com/cyngn/theme/util/Utils.java delete mode 100644 src/com/cyngn/theme/util/WallpaperUtils.java delete mode 100644 src/com/cyngn/theme/widget/AutoSnapHorizontalScrollView.java delete mode 100644 src/com/cyngn/theme/widget/BootAniImageView.java delete mode 100644 src/com/cyngn/theme/widget/ConfirmCancelOverlay.java delete mode 100644 src/com/cyngn/theme/widget/FittedTextView.java delete mode 100644 src/com/cyngn/theme/widget/LatoTextView.java delete mode 100644 src/com/cyngn/theme/widget/LockableScrollView.java delete mode 100644 src/com/cyngn/theme/widget/NavBarSpace.java delete mode 100644 src/com/cyngn/theme/widget/ThemeTagLayout.java create mode 100644 src/org/cyanogenmod/theme/chooser/AppReceiver.java create mode 100644 src/org/cyanogenmod/theme/chooser/BootReceiver.java create mode 100644 src/org/cyanogenmod/theme/chooser/ChooserActivity.java create mode 100644 src/org/cyanogenmod/theme/chooser/ComponentCardView.java create mode 100644 src/org/cyanogenmod/theme/chooser/ComponentSelector.java create mode 100644 src/org/cyanogenmod/theme/chooser/IconTransitionDrawable.java create mode 100644 src/org/cyanogenmod/theme/chooser/MyThemeFragment.java create mode 100644 src/org/cyanogenmod/theme/chooser/NewFragmentStatePagerAdapter.java create mode 100644 src/org/cyanogenmod/theme/chooser/NotificationHijackingService.java create mode 100644 src/org/cyanogenmod/theme/chooser/PagerContainer.java create mode 100644 src/org/cyanogenmod/theme/chooser/ThemeFragment.java create mode 100644 src/org/cyanogenmod/theme/chooser/WallpaperCardView.java create mode 100644 src/org/cyanogenmod/theme/perapptheming/PerAppThemeListLayout.java create mode 100644 src/org/cyanogenmod/theme/perapptheming/PerAppThemeListView.java create mode 100644 src/org/cyanogenmod/theme/perapptheming/PerAppThemingWindow.java create mode 100644 src/org/cyanogenmod/theme/util/AudioUtils.java create mode 100644 src/org/cyanogenmod/theme/util/BootAnimationHelper.java create mode 100644 src/org/cyanogenmod/theme/util/CursorLoaderHelper.java create mode 100644 src/org/cyanogenmod/theme/util/FontConfigParser.java create mode 100644 src/org/cyanogenmod/theme/util/IconPreviewHelper.java create mode 100644 src/org/cyanogenmod/theme/util/NotificationHelper.java create mode 100644 src/org/cyanogenmod/theme/util/PreferenceUtils.java create mode 100644 src/org/cyanogenmod/theme/util/ThemedTypefaceHelper.java create mode 100644 src/org/cyanogenmod/theme/util/TypefaceHelperCache.java create mode 100644 src/org/cyanogenmod/theme/util/Utils.java create mode 100644 src/org/cyanogenmod/theme/util/WallpaperUtils.java create mode 100644 src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java create mode 100644 src/org/cyanogenmod/theme/widget/BootAniImageView.java create mode 100644 src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java create mode 100644 src/org/cyanogenmod/theme/widget/FittedTextView.java create mode 100644 src/org/cyanogenmod/theme/widget/LatoTextView.java create mode 100644 src/org/cyanogenmod/theme/widget/LockableScrollView.java create mode 100644 src/org/cyanogenmod/theme/widget/NavBarSpace.java create mode 100644 src/org/cyanogenmod/theme/widget/ThemeTagLayout.java (limited to 'src') diff --git a/src/android/support/v4/view/ThemeViewPager.java b/src/android/support/v4/view/ThemeViewPager.java index cae4d5a..ba0911e 100644 --- a/src/android/support/v4/view/ThemeViewPager.java +++ b/src/android/support/v4/view/ThemeViewPager.java @@ -1,6 +1,20 @@ /* - * Copyright (C) 2014 Cyanogen, Inc. + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 android.support.v4.view; import android.content.Context; @@ -10,7 +24,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; -import com.cyngn.theme.chooser.R; +import org.cyanogenmod.theme.chooser.R; public class ThemeViewPager extends ViewPager { private static final String TAG = ThemeViewPager.class.getSimpleName(); diff --git a/src/android/widget/ComponentSelectorLinearLayout.java b/src/android/widget/ComponentSelectorLinearLayout.java index cb13ba0..17d4256 100644 --- a/src/android/widget/ComponentSelectorLinearLayout.java +++ b/src/android/widget/ComponentSelectorLinearLayout.java @@ -1,6 +1,20 @@ /* - * Copyright (C) 2014 The Cyanogen, Inc + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 android.widget; import android.content.Context; diff --git a/src/com/cyngn/theme/chooser/AppReceiver.java b/src/com/cyngn/theme/chooser/AppReceiver.java deleted file mode 100644 index 4354f63..0000000 --- a/src/com/cyngn/theme/chooser/AppReceiver.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.chooser; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.net.Uri; -import android.text.TextUtils; - -import com.cyngn.theme.util.NotificationHelper; -import com.cyngn.theme.util.PreferenceUtils; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.providers.ThemesContract; - -public class AppReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - Uri uri = intent.getData(); - String pkgName = uri != null ? uri.getSchemeSpecificPart() : null; - String action = intent.getAction(); - - if (cyanogenmod.content.Intent.ACTION_THEME_INSTALLED.equals(action)) { - if (!pkgName.equals(Utils.getDefaultThemePackageName(context))) { - NotificationHelper.postThemeInstalledNotification(context, pkgName); - } - } else if (cyanogenmod.content.Intent.ACTION_THEME_REMOVED.equals(action)) { - // remove updated status for this theme (if one exists) - PreferenceUtils.removeUpdatedTheme(context, pkgName); - - // If the theme being removed was the currently applied theme we need - // to update the applied base theme in preferences to the default theme. - String appliedBaseTheme = PreferenceUtils.getAppliedBaseTheme(context); - if (!TextUtils.isEmpty(appliedBaseTheme) && appliedBaseTheme.equals(pkgName)) { - PreferenceUtils.setAppliedBaseTheme(context, - Utils.getDefaultThemePackageName(context)); - } - NotificationHelper.cancelNotifications(context); - } else if (cyanogenmod.content.Intent.ACTION_THEME_UPDATED.equals(action)) { - try { - if (isTheme(context, pkgName)) { - PreferenceUtils.addUpdatedTheme(context, pkgName); - } - } catch (NameNotFoundException e) { - } - } - } - - private boolean isTheme(Context context, String pkgName) throws NameNotFoundException { - PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); - - return pi != null && pi.themeInfo != null; - } -} diff --git a/src/com/cyngn/theme/chooser/BootReceiver.java b/src/com/cyngn/theme/chooser/BootReceiver.java deleted file mode 100644 index 8240bfc..0000000 --- a/src/com/cyngn/theme/chooser/BootReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - -public class BootReceiver extends BroadcastReceiver { - private static final String CHOOSER_PKG_NAME = "com.cyngn.theme.chooser"; - private static final String CHOOSER_ACTIVITY = "com.cyngn.theme.chooser.ChooserLauncher"; - - @Override - public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - PackageManager pm = context.getPackageManager(); - if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { - try { - PackageInfo info = pm.getPackageInfo(ChooserActivity.THEME_STORE_PACKAGE, 0); - if (info != null) { - ComponentName cn = new ComponentName(CHOOSER_PKG_NAME, CHOOSER_ACTIVITY); - pm.setComponentEnabledSetting(cn, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } - } catch (PackageManager.NameNotFoundException e) { - // no store so nothing to do. - } - - // now disable this receiver so we don't get called on future boots - ComponentName cn = new ComponentName(CHOOSER_PKG_NAME, - BootReceiver.class.getCanonicalName()); - pm.setComponentEnabledSetting(cn, - PackageManager.COMPONENT_ENABLED_STATE_DISABLED, - PackageManager.DONT_KILL_APP); - } - } -} diff --git a/src/com/cyngn/theme/chooser/ChooserActivity.java b/src/com/cyngn/theme/chooser/ChooserActivity.java deleted file mode 100644 index d607eb6..0000000 --- a/src/com/cyngn/theme/chooser/ChooserActivity.java +++ /dev/null @@ -1,1191 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.chooser; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.IPackageDeleteObserver; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.Paint; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.os.RemoteException; -import android.provider.Settings; -import android.renderscript.Allocation; -import android.renderscript.Element; -import android.renderscript.RenderScript; -import android.renderscript.ScriptIntrinsicBlur; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.support.v4.view.ThemeViewPager; -import android.support.v4.view.ViewPager; -import android.text.TextUtils; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.MutableLong; -import android.util.TypedValue; -import android.view.View; -import android.view.ViewPropertyAnimator; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.animation.DecelerateInterpolator; -import android.widget.ImageView; - -import com.cyngn.theme.perapptheming.PerAppThemingWindow; -import com.cyngn.theme.util.CursorLoaderHelper; -import com.cyngn.theme.util.NotificationHelper; -import com.cyngn.theme.util.PreferenceUtils; -import com.cyngn.theme.util.TypefaceHelperCache; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.platform.Manifest; -import cyanogenmod.providers.ThemesContract; -import cyanogenmod.providers.ThemesContract.ThemesColumns; -import cyanogenmod.themes.ThemeManager; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; - -import static com.cyngn.theme.chooser.ComponentSelector.DEFAULT_COMPONENT_ID; - -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_INSTALLED_THEMES; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_APPLIED; - -public class ChooserActivity extends FragmentActivity - implements LoaderManager.LoaderCallbacks { - public static final String THEME_STORE_PACKAGE = "com.cyngn.themestore"; - private static final String TAG = ChooserActivity.class.getSimpleName(); - - public static final String DEFAULT = ThemeConfig.SYSTEM_DEFAULT; - public static final String EXTRA_PKGNAME = "pkgName"; - public static final String EXTRA_COMPONENTS = "components"; - - private static final int OFFSCREEN_PAGE_LIMIT = 3; - - private static final String THEME_STORE_ACTIVITY = THEME_STORE_PACKAGE + ".ui.StoreActivity"; - private static final String ACTION_APPLY_THEME = "android.intent.action.APPLY_THEME"; - - private static final String TYPE_IMAGE = "image/*"; - - private static final String CYNGN_THEMES_PERMISSION = - "com.cyngn.themes.permission.THEMES_APP"; - private static final String ACTION_CHOOSER_OPENED = - "com.cyngn.themes.action.CHOOSER_OPENED"; - private static final String ACTION_THEME_REMOVED = - "com.cyngn.themes.action.THEME_REMOVED_FROM_CHOOSER"; - private static final String EXTRA_PACKAGE = "package"; - - private static final String ACTION_PICK_LOCK_SCREEN_WALLPAPER = - "com.cyngn.intent.action.PICK_LOCK_SCREEN_WALLPAPER"; - - /** - * Request code for picking an external wallpaper - */ - public static final int REQUEST_PICK_WALLPAPER_IMAGE = 2; - /** - * Request code for picking an external lockscreen wallpaper - */ - public static final int REQUEST_PICK_LOCKSCREEN_IMAGE = 3; - - /** - * Request code for enabling system alert window permission - */ - private static final int REQUEST_SYSTEM_WINDOW_PERMISSION = 4; - - private static final long ANIMATE_CONTENT_IN_SCALE_DURATION = 500; - private static final long ANIMATE_CONTENT_IN_ALPHA_DURATION = 750; - private static final long ANIMATE_CONTENT_IN_BLUR_DURATION = 250; - private static final long ANIMATE_CONTENT_DELAY = 250; - private static final long ANIMATE_SHOP_THEMES_HIDE_DURATION = 250; - private static final long ANIMATE_SHOP_THEMES_SHOW_DURATION = 500; - private static final long FINISH_ANIMATION_DELAY = ThemeFragment.ANIMATE_DURATION - + ThemeFragment.ANIMATE_START_DELAY + 250; - - private static final long ANIMATE_CARDS_IN_DURATION = 250; - private static final long ANIMATE_SAVE_APPLY_LAYOUT_DURATION = 300; - private static final float ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR = 3; - private static final long ONCLICK_SAVE_APPLY_FINISH_ANIMATION_DELAY = 400; - - private PagerContainer mContainer; - private ThemeViewPager mPager; - - private ThemesAdapter mAdapter; - private boolean mExpanded = false; - private ComponentSelector mSelector; - private View mSaveApplyLayout; - private int mContainerYOffset = 0; - private TypefaceHelperCache mTypefaceHelperCache; - private boolean mIsAnimating; - private Handler mHandler; - private View mBottomActionsLayout; - - private String mSelectedTheme; - private String mAppliedBaseTheme; - private boolean mThemeChanging = false; - private boolean mAnimateContentIn = false; - private long mAnimateContentInDelay; - private String mThemeToApply; - private ArrayList mComponentsToApply; - - ImageView mCustomBackground; - - // Current system theme configuration as component -> pkgName - private Map mCurrentTheme = new HashMap(); - private MutableLong mCurrentWallpaperCmpntId = new MutableLong(DEFAULT_COMPONENT_ID); - - private boolean mIsPickingImage = false; - private boolean mRestartLoaderOnCollapse = false; - private boolean mActivityResuming = false; - private boolean mShowLockScreenWallpaper = false; - - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - NotificationHijackingService.ensureEnabled(this); - - if (savedInstanceState == null) { - handleIntent(getIntent()); - } - - mContainer = (PagerContainer) findViewById(R.id.pager_container); - mPager = (ThemeViewPager) findViewById(R.id.viewpager); - - mPager.setOnClickListener(mPagerClickListener); - mAdapter = new ThemesAdapter(); - mPager.setAdapter(mAdapter); - - DisplayMetrics dm = getResources().getDisplayMetrics(); - int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, dm); - mPager.setPageMargin(-margin / 2); - mPager.setOffscreenPageLimit(OFFSCREEN_PAGE_LIMIT); - - mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { - public void onPageSelected(int position) { - } - - public void onPageScrolled(int position, - float positionOffset, - int positionOffsetPixels) { - } - - public void onPageScrollStateChanged(int state) { - } - }); - - mSelector = (ComponentSelector) findViewById(R.id.component_selector); - mSelector.setOnOpenCloseListener(mOpenCloseListener); - - mBottomActionsLayout = findViewById(R.id.bottom_actions_layout); - - mSaveApplyLayout = findViewById(R.id.save_apply_layout); - mSaveApplyLayout.findViewById(R.id.save_apply_button).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mIsAnimating) return; - hideSaveApplyButton(); - mContainer.setClickable(false); - final ThemeFragment f = getCurrentFragment(); - if (mSelector.isEnabled()) { - mSelector.hide(); - if (mContainerYOffset != 0) { - slideContentBack(-mContainerYOffset); - mContainerYOffset = 0; - } - if (f != null) f.fadeInCards(); - if (mShowLockScreenWallpaper) { - mShowLockScreenWallpaper = false; - mSelector.resetComponentType(); - } - } - - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - collapse(true); - } - }, ONCLICK_SAVE_APPLY_FINISH_ANIMATION_DELAY); - } - }); - - mBottomActionsLayout.findViewById(R.id.shop_themes) - .setOnClickListener(mOnShopThemesClicked); - - mTypefaceHelperCache = TypefaceHelperCache.getInstance(); - mHandler = new Handler(); - mCustomBackground = (ImageView) findViewById(R.id.custom_bg); - mAnimateContentIn = true; - mAnimateContentInDelay = 0; - - mBottomActionsLayout.findViewById(R.id.per_app_theming).setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (Settings.canDrawOverlays(ChooserActivity.this)) { - launchAppThemer(); - } else { - requestSystemWindowPermission(); - } - } - }); - - if (shouldHideShopThemes()) { - mBottomActionsLayout.findViewById(R.id.shop_themes).setVisibility(View.GONE); - } - if (PreferenceUtils.getShowPerAppThemeNewTag(this)) { - View tag = mBottomActionsLayout.findViewById(R.id.new_tag); - if (tag != null) { - tag.setVisibility(View.VISIBLE); - } - } - } - - public void showSaveApplyButton() { - if (mSaveApplyLayout != null && mSaveApplyLayout.getVisibility() != View.VISIBLE) { - mHandler.post(new Runnable() { - @Override - public void run() { - int navBarHeight = 0; - if (Utils.hasNavigationBar(ChooserActivity.this.getApplicationContext())) { - navBarHeight = ChooserActivity.this.getResources() - .getDimensionPixelSize(R.dimen.navigation_bar_height); - } - mSaveApplyLayout.setTranslationY(mSaveApplyLayout.getMeasuredHeight()); - mSaveApplyLayout.setVisibility(View.VISIBLE); - mSaveApplyLayout.animate() - .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION) - .setInterpolator( - new DecelerateInterpolator( - ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR)) - .translationY(-mSelector.getMeasuredHeight() - + navBarHeight); - } - }); - } - } - - public void hideSaveApplyButton() { - if (mSaveApplyLayout.getVisibility() != View.GONE) { - Animation anim = AnimationUtils.loadAnimation(this, - R.anim.component_selection_animate_out); - mSaveApplyLayout.startAnimation(anim); - anim.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - mSaveApplyLayout.setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - } - } - - private void hideBottomActionsLayout() { - final ViewPropertyAnimator anim = mBottomActionsLayout.animate(); - anim.alpha(0f).setDuration(ANIMATE_SHOP_THEMES_HIDE_DURATION); - anim.setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mBottomActionsLayout.setVisibility(View.GONE); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - } - - private void showBottomActionsLayout() { - mBottomActionsLayout.setVisibility(View.VISIBLE); - final ViewPropertyAnimator anim = mBottomActionsLayout.animate(); - anim.setListener(null); - anim.alpha(1f).setStartDelay(ThemeFragment.ANIMATE_DURATION) - .setDuration(ANIMATE_SHOP_THEMES_SHOW_DURATION); - } - - private boolean shouldHideShopThemes() { - boolean hasThemeStore = false; - try { - if (getPackageManager().getPackageInfo(THEME_STORE_PACKAGE, 0) != null) { - hasThemeStore = true; - } - } catch (PackageManager.NameNotFoundException e) { - - } - return !hasThemeStore || Utils.isRecentTaskThemeStore(this); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); - if (Utils.isRecentTaskHome(this)) { - mContainer.setAlpha(0f); - mBottomActionsLayout.setAlpha(0f); - mAnimateContentIn = true; - mAnimateContentInDelay = ANIMATE_CONTENT_DELAY; - } - handleIntent(intent); - } - - private void handleIntent(Intent intent) { - String action = intent.getAction(); - if ((Intent.ACTION_MAIN.equals(action) || ACTION_APPLY_THEME.equals(action)) - && intent.hasExtra(EXTRA_PKGNAME)) { - if (intent.hasExtra(EXTRA_COMPONENTS)) { - mComponentsToApply = intent.getStringArrayListExtra(EXTRA_COMPONENTS); - } else { - mComponentsToApply = null; - } - mSelectedTheme = mComponentsToApply != null ? - PreferenceUtils.getAppliedBaseTheme(this) : - getSelectedTheme(intent.getStringExtra(EXTRA_PKGNAME)); - if (mPager != null) { - startLoader(LOADER_ID_INSTALLED_THEMES); - if (mExpanded) { - int collapseDelay = ThemeFragment.ANIMATE_START_DELAY; - if (mSelector.isEnabled()) { - // onBackPressed() has all the necessary logic for collapsing the - // component selector, so we call it here. - onBackPressed(); - collapseDelay += ThemeFragment.ANIMATE_DURATION; - } - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - collapse(false); - } - }, collapseDelay); - } - } - - if (ACTION_APPLY_THEME.equals(action) && - getCallingPackage() != null && - PackageManager.PERMISSION_GRANTED == - getPackageManager() - .checkPermission(Manifest.permission.WRITE_THEMES, - getCallingPackage())) { - mThemeToApply = intent.getStringExtra(EXTRA_PKGNAME); - } - } else if (ACTION_PICK_LOCK_SCREEN_WALLPAPER.equals(action)) { - mShowLockScreenWallpaper = true; - } - } - - private String getSelectedTheme(String requestedTheme) { - String[] projection = { ThemesColumns.PRESENT_AS_THEME }; - String selection = ThemesColumns.PKG_NAME + "=?"; - String[] selectionArgs = { requestedTheme }; - - String selectedTheme = PreferenceUtils.getAppliedBaseTheme(this); - - Cursor cursor = getContentResolver().query(ThemesColumns.CONTENT_URI, - projection, selection, selectionArgs, null); - if (cursor != null) { - if (cursor.getCount() > 0 && cursor.moveToFirst()) { - if (cursor.getInt(0) == 1) { - selectedTheme = requestedTheme; - } - } - cursor.close(); - } - return selectedTheme; - } - - private void setAnimatingStateAndScheduleFinish() { - mIsAnimating = true; - mContainer.setIsAnimating(true); - mHandler.postDelayed(new Runnable() { - public void run() { - mIsAnimating = false; - mContainer.setIsAnimating(false); - if (mRestartLoaderOnCollapse) { - mRestartLoaderOnCollapse = false; - startLoader(LOADER_ID_INSTALLED_THEMES); - } - } - }, FINISH_ANIMATION_DELAY); - if (mExpanded) { - hideBottomActionsLayout(); - } else { - showBottomActionsLayout(); - } - } - - private void setCustomBackground(final ImageView iv, final boolean animate) { - final Context context = ChooserActivity.this; - iv.post(new Runnable() { - @Override - public void run() { - Bitmap tmpBmp; - try { - tmpBmp = Utils.getRegularWallpaperBitmap(context); - } catch (Throwable e) { - Log.w(TAG, "Failed to retrieve wallpaper", e); - tmpBmp = null; - } - // Show the grid background if no wallpaper is set. - // Note: no wallpaper is actually a 1x1 pixel black bitmap - if (tmpBmp == null || tmpBmp.getWidth() <= 1 || tmpBmp.getHeight() <= 1) { - iv.setImageResource(R.drawable.bg_grid); - // We need to change the ScaleType to FIT_XY otherwise the background is cut - // off a bit at the bottom. - iv.setScaleType(ImageView.ScaleType.FIT_XY); - return; - } - - // Since we are applying a blur, we can afford to scale the bitmap down and use a - // smaller blur radius. - Bitmap inBmp = Bitmap.createScaledBitmap(tmpBmp, tmpBmp.getWidth() / 4, - tmpBmp.getHeight() / 4, false); - Bitmap outBmp = Bitmap.createBitmap(inBmp.getWidth(), inBmp.getHeight(), - Bitmap.Config.ARGB_8888); - - // Blur the original bitmap - RenderScript rs = RenderScript.create(context); - ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); - Allocation tmpIn = Allocation.createFromBitmap(rs, inBmp); - Allocation tmpOut = Allocation.createFromBitmap(rs, outBmp); - theIntrinsic.setRadius(5.0f); - theIntrinsic.setInput(tmpIn); - theIntrinsic.forEach(tmpOut); - tmpOut.copyTo(outBmp); - - // Create a bitmap drawable and use a color matrix to de-saturate the image - BitmapDrawable[] layers = new BitmapDrawable[2]; - layers[0] = new BitmapDrawable(getResources(), tmpBmp); - layers[1] = new BitmapDrawable(getResources(), outBmp); - ColorMatrix cm = new ColorMatrix(); - cm.setSaturation(0); - Paint p = layers[0].getPaint(); - p.setColorFilter(new ColorMatrixColorFilter(cm)); - p = layers[1].getPaint(); - p.setColorFilter(new ColorMatrixColorFilter(cm)); - TransitionDrawable d = new TransitionDrawable(layers); - - // All done - iv.setScaleType(ImageView.ScaleType.CENTER_CROP); - if (!animate) { - iv.setImageDrawable(layers[1]); - } else { - iv.setImageDrawable(d); - } - } - }); - } - - /** - * Disable the ViewPager while a theme change is occuring - */ - public void themeChangeStart() { - lockPager(); - mThemeChanging = true; - ThemeFragment f = getCurrentFragment(); - if (f != null) { - mAppliedBaseTheme = f.getThemePackageName(); - PreferenceUtils.setAppliedBaseTheme(this, mAppliedBaseTheme); - } - } - - /** - * Re-enable the ViewPager and update the "My theme" fragment if available - */ - public void themeChangeEnd(boolean isSuccess) { - mThemeChanging = false; - ThemeFragment f = getCurrentFragment(); - if (f != null) { - // We currently need to recreate the adapter in order to load - // the changes otherwise the adapter returns the original fragments - // TODO: We'll need a better way to handle this to provide a good UX - if (!(f instanceof MyThemeFragment)) { - mAdapter = new ThemesAdapter(); - mPager.setAdapter(mAdapter); - } - if (!isSuccess) { - mAppliedBaseTheme = null; - } - startLoader(LOADER_ID_APPLIED); - } - unlockPager(); - } - - public void lockPager() { - mPager.setScrollingEnabled(false); - } - - public void unlockPager() { - mPager.setScrollingEnabled(true); - } - - public ComponentSelector getComponentSelector() { - return mSelector; - } - - public void showComponentSelector(String component, View v) { - showComponentSelector(component, null, DEFAULT_COMPONENT_ID, v); - } - - public void showComponentSelector(String component, String selectedPkgName, - long selectedCmpntId, View v) { - if (component != null) { - final Resources res = getResources(); - int itemsPerPage = res.getInteger(R.integer.default_items_per_page); - int height = res.getDimensionPixelSize(R.dimen.component_selection_cell_height); - if (MODIFIES_BOOT_ANIM.equals(component)) { - itemsPerPage = res.getInteger(R.integer.bootani_items_per_page); - height = res.getDimensionPixelSize( - R.dimen.component_selection_cell_height_boot_anim); - } else if (MODIFIES_ALARMS.equals(component) || - MODIFIES_NOTIFICATIONS.equals(component) || - MODIFIES_RINGTONES.equals(component)) { - itemsPerPage = 2; - height = res.getDimensionPixelSize( - R.dimen.component_selection_cell_height_sounds); - } - if (mSaveApplyLayout.getVisibility() == View.VISIBLE) { - if (mSaveApplyLayout.getTranslationY() + height != 0) { - mSaveApplyLayout.animate() - .translationY(-height) - .setInterpolator( - new DecelerateInterpolator( - ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR)) - .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION); - } - } - mSelector.show(component, selectedPkgName, selectedCmpntId, itemsPerPage, height); - - // determine if we need to shift the cards up - int[] coordinates = new int[2]; - v.getLocationOnScreen(coordinates); - final int top = coordinates[1]; - final int bottom = coordinates[1] + v.getHeight(); - final int statusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); - int selectorTop = getWindowManager().getDefaultDisplay().getHeight() - height; - if (bottom > selectorTop) { - slideContentIntoView(bottom - selectorTop, height); - } else if (top < statusBarHeight) { - slideContentIntoView(top - statusBarHeight, height); - } - } - } - - public void expand() { - if (!mExpanded && !mIsAnimating) { - mExpanded = true; - mContainer.setClickable(false); - mContainer.expand(); - ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.expand(); - } - setAnimatingStateAndScheduleFinish(); - } - } - - public void collapse(final boolean applyTheme) { - mExpanded = false; - final ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.fadeOutCards(new Runnable() { - public void run() { - mContainer.collapse(); - f.collapse(applyTheme); - } - }); - } - setAnimatingStateAndScheduleFinish(); - } - - public void pickExternalWallpaper() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType(TYPE_IMAGE); - startActivityForResult(intent, REQUEST_PICK_WALLPAPER_IMAGE); - mIsPickingImage = true; - } - - public void pickExternalLockscreen() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType(TYPE_IMAGE); - startActivityForResult(intent, REQUEST_PICK_LOCKSCREEN_IMAGE); - mIsPickingImage = true; - } - - public void uninstallTheme(String pkgName) { - PackageManager pm = getPackageManager(); - pm.deletePackage(pkgName, new PackageDeleteObserver(), PackageManager.DELETE_ALL_USERS); - sendThemeRemovedBroadcast(pkgName); - } - - private void slideContentIntoView(int yDelta, int selectorHeight) { - ThemeFragment f = getCurrentFragment(); - if (f != null) { - final int offset = getResources().getDimensionPixelSize(R.dimen.content_offset_padding); - if (yDelta > 0) { - yDelta += offset; - } else { - yDelta -= offset; - } - f.slideContentIntoView(yDelta, selectorHeight); - mContainerYOffset = yDelta; - } - } - - private void slideContentBack(final int yDelta) { - ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.slideContentBack(yDelta); - } - } - - @Override - protected void onResume() { - super.onResume(); - setCustomBackground(mCustomBackground, mAnimateContentIn); - // clear out any notifications that are being displayed. - NotificationHelper.cancelNotifications(this); - - ThemeManager tm = ThemeManager.getInstance(this); - mThemeChanging = tm.isThemeApplying(); - - if (!mIsPickingImage) { - startLoader(LOADER_ID_APPLIED); - } else { - mIsPickingImage = false; - } - - IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); - registerReceiver(mWallpaperChangeReceiver, filter); - } - - @Override - public void onBackPressed() { - final ThemeFragment f = getCurrentFragment(); - if (mSelector.isEnabled()) { - mSelector.hide(); - if (mContainerYOffset != 0) { - slideContentBack(-mContainerYOffset); - mContainerYOffset = 0; - } - if (f != null) f.fadeInCards(); - if (mShowLockScreenWallpaper) { - mShowLockScreenWallpaper = false; - mSelector.resetComponentType(); - } - } else if (mExpanded) { - if (mIsAnimating) { - return; - } - - if (mSaveApplyLayout.getVisibility() == View.VISIBLE) { - hideSaveApplyButton(); - if (f != null) f.clearChanges(); - } - collapse(false); - } else { - if (f != null && f.isShowingConfirmCancelOverlay()) { - f.hideConfirmCancelOverlay(); - } else if (f != null && f.isShowingCustomizeResetLayout()) { - f.hideCustomizeResetLayout(); - } else { - super.onBackPressed(); - } - } - } - - @Override - public void onPause() { - super.onPause(); - unregisterReceiver(mWallpaperChangeReceiver); - ThemeFragment f = getCurrentFragment(); - if (f != null) { - mSelectedTheme = f.getThemePackageName(); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - } - - @Override - protected void onStart() { - super.onStart(); - if (mTypefaceHelperCache.getTypefaceCount() <= 0) { - new TypefacePreloadTask().execute(); - } - sendChooserOpenedBroadcast(); - mAnimateContentInDelay = ANIMATE_CONTENT_DELAY; - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_WALLPAPER_IMAGE) { - if (data != null && data.getData() != null) { - Uri uri = data.getData(); - ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.setWallpaperImageUri(uri); - showSaveApplyButton(); - } - } - } else if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_LOCKSCREEN_IMAGE) { - if (data != null && data.getData() != null) { - Uri uri = data.getData(); - ThemeFragment f = getCurrentFragment(); - if (f != null) { - f.setLockscreenImageUri(uri); - showSaveApplyButton(); - } - } - } else if (requestCode == REQUEST_SYSTEM_WINDOW_PERMISSION) { - if (Settings.canDrawOverlays(this)) { - launchAppThemer(); - } - } - } - - private void sendChooserOpenedBroadcast() { - sendBroadcast(new Intent(ACTION_CHOOSER_OPENED), CYNGN_THEMES_PERMISSION); - } - - private void sendThemeRemovedBroadcast(String pkgName) { - Intent intent = new Intent(ACTION_THEME_REMOVED); - intent.putExtra(EXTRA_PACKAGE, pkgName); - sendBroadcast(intent, CYNGN_THEMES_PERMISSION); - } - - private void animateContentIn() { - Drawable d = mCustomBackground.getDrawable(); - if (d instanceof TransitionDrawable) { - ((TransitionDrawable) d).startTransition((int) ANIMATE_CONTENT_IN_BLUR_DURATION); - } - - if (!mShowLockScreenWallpaper) { - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f) - .setDuration(ANIMATE_CONTENT_IN_ALPHA_DURATION)) - .with(ObjectAnimator.ofFloat(mContainer, "scaleX", 2f, 1f) - .setDuration(ANIMATE_CONTENT_IN_SCALE_DURATION)) - .with(ObjectAnimator.ofFloat(mContainer, "scaleY", 2f, 1f) - .setDuration(ANIMATE_CONTENT_IN_SCALE_DURATION)); - set.setStartDelay(mAnimateContentInDelay); - set.start(); - mBottomActionsLayout.setAlpha(0f); - mBottomActionsLayout.animate().alpha(1f).setStartDelay(mAnimateContentInDelay) - .setDuration(ANIMATE_CONTENT_IN_ALPHA_DURATION); - } else { - mContainer.setAlpha(0f); - mContainer.setVisibility(View.GONE); - } - mAnimateContentIn = false; - } - - private View.OnClickListener mPagerClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - ThemeFragment f = getCurrentFragment(); - if (f != null && !mThemeChanging) { - f.performClick(mPager.isClickedOnContent()); - } - } - }; - - private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (mCustomBackground != null) setCustomBackground(mCustomBackground, false); - } - }; - - private ComponentSelector.OnOpenCloseListener mOpenCloseListener = new ComponentSelector.OnOpenCloseListener() { - @Override - public void onSelectorOpened() { - } - - @Override - public void onSelectorClosed() { - } - - @Override - public void onSelectorClosing() { - ThemeFragment f = getCurrentFragment(); - if (f != null && f.componentsChanged() - && mSaveApplyLayout.getVisibility() == View.VISIBLE) { - mSaveApplyLayout.animate() - .translationY(0) - .setInterpolator(new DecelerateInterpolator()) - .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION); - } - } - }; - - private ThemeFragment getCurrentFragment() { - // instantiateItem will return the fragment if it already exists and not instantiate it, - // which should be the case for the current fragment. - ThemeFragment f; - try { - f = (mAdapter == null || mPager == null || mAdapter.getCount() <= 0) ? null : - (ThemeFragment) mAdapter.instantiateItem(mPager, mPager.getCurrentItem()); - } catch (Exception e) { - f = null; - Log.e(TAG, "Unable to get current fragment", e); - } - return f; - } - - private void populateCurrentTheme(Cursor c) { - c.moveToPosition(-1); - //Default to first wallpaper - mCurrentWallpaperCmpntId.value = DEFAULT_COMPONENT_ID; - // clear out the previous map - mCurrentTheme.clear(); - while(c.moveToNext()) { - int mixkeyIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_KEY); - int pkgIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_VALUE); - int cmpntIdIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_COMPONENT_ID); - String mixkey = c.getString(mixkeyIdx); - String component = ThemesContract.MixnMatchColumns.mixNMatchKeyToComponent(mixkey); - String pkg = c.getString(pkgIdx); - mCurrentTheme.put(component, pkg); - if (TextUtils.equals(component, ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) { - mCurrentTheme.remove(ThemesColumns.MODIFIES_LOCKSCREEN); - } - if (TextUtils.equals(component, ThemesColumns.MODIFIES_LOCKSCREEN)) { - mCurrentTheme.remove(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN); - } - if (cmpntIdIdx >= 0 && TextUtils.equals(component, ThemesColumns.MODIFIES_LAUNCHER)) { - mCurrentWallpaperCmpntId.value = c.getLong(cmpntIdIdx); - } - } - } - - private View.OnClickListener mOnShopThemesClicked = new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(); - intent.setClassName(THEME_STORE_PACKAGE, THEME_STORE_ACTIVITY); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - try { - startActivity(intent); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "Unable to launch Theme Store", e); - } - } - }; - - private void startLoader(int loaderId) { - final LoaderManager manager = getSupportLoaderManager(); - final Loader loader = manager.getLoader(loaderId); - if (loader != null) { - manager.restartLoader(loaderId, null, this); - } else { - manager.initLoader(loaderId, null, this); - } - } - - @Override - public void onLoadFinished(Loader loader, Cursor data) { - if (mThemeChanging) return; - - if (mExpanded && !mActivityResuming) { - mRestartLoaderOnCollapse = true; - return; - } - - switch (loader.getId()) { - case LOADER_ID_INSTALLED_THEMES: - // Swap the new cursor in. (The framework will take care of closing the - // old cursor once we return.) - int selectedThemeIndex = -1; - if (TextUtils.isEmpty(mSelectedTheme)) mSelectedTheme = mAppliedBaseTheme; - while(data.moveToNext()) { - if (mSelectedTheme.equals(data.getString( - data.getColumnIndex(ThemesColumns.PKG_NAME)))) { - // we need to add one here since the first card is "My theme" - selectedThemeIndex = data.getPosition(); - mSelectedTheme = null; - break; - } - } - data.moveToFirst(); - mAdapter.swapCursor(data); - mAdapter.notifyDataSetChanged(); - if (selectedThemeIndex >= 0) { - mPager.setCurrentItem(selectedThemeIndex, false); - - if (mThemeToApply != null) { - ThemeFragment f = getCurrentFragment(); - f.applyThemeWhenPopulated(mThemeToApply, mComponentsToApply); - mThemeToApply = null; - } - } - if (mAnimateContentIn) animateContentIn(); - mActivityResuming = true; - break; - case LOADER_ID_APPLIED: - startLoader(LOADER_ID_INSTALLED_THEMES); - populateCurrentTheme(data); - break; - } - } - - @Override - public void onLoaderReset(Loader loader) { - switch (loader.getId()) { - case LOADER_ID_INSTALLED_THEMES: - mAdapter.swapCursor(null); - mAdapter.notifyDataSetChanged(); - break; - } - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_INSTALLED_THEMES: - mAppliedBaseTheme = PreferenceUtils.getAppliedBaseTheme(this); - break; - case LOADER_ID_APPLIED: - //TODO: Mix n match query should only be done once - break; - } - return CursorLoaderHelper.chooserActivityCursorLoader(this, id, mAppliedBaseTheme); - } - - public Map getSelectedComponentsMap() { - return getCurrentFragment().getSelectedComponentsMap(); - } - - public class ThemesAdapter extends NewFragmentStatePagerAdapter { - private ArrayList mInstalledThemes; - private String mAppliedThemeTitle; - private String mAppliedThemeAuthor; - private HashMap mRepositionedFragments; - - public ThemesAdapter() { - super(getSupportFragmentManager()); - mRepositionedFragments = new HashMap(); - } - - @Override - public Fragment getItem(int position) { - ThemeFragment f = null; - MutableLong wallpaperCmpntId; - if (mInstalledThemes != null) { - final String pkgName = mInstalledThemes.get(position); - if (pkgName.equals(mAppliedBaseTheme)) { - f = MyThemeFragment.newInstance(mAppliedBaseTheme, mAppliedThemeTitle, - mAppliedThemeAuthor, mAnimateContentIn, mShowLockScreenWallpaper); - wallpaperCmpntId = mCurrentWallpaperCmpntId; - } else { - f = ThemeFragment.newInstance(pkgName, mAnimateContentIn); - wallpaperCmpntId = new MutableLong(DEFAULT_COMPONENT_ID); - } - f.setCurrentTheme(mCurrentTheme, wallpaperCmpntId); - } - return f; - } - - @Override - public long getItemId(int position) { - if (mInstalledThemes != null) { - final String pkgName = mInstalledThemes.get(position); - return pkgName.hashCode(); - } - return 0; - } - - @Override - public int getItemPosition(Object object) { - final ThemeFragment f = (ThemeFragment) object; - final String pkgName = f != null ? f.getThemePackageName() : null; - if (pkgName != null && mRepositionedFragments.containsKey(pkgName)) { - final int position = mRepositionedFragments.get(pkgName); - mRepositionedFragments.remove(pkgName); - return position; - } - return super.getItemPosition(object); - } - - /** - * The first card should be the user's currently applied theme components so we - * will always return at least 1 or mCursor.getCount() + 1 - * @return - */ - public int getCount() { - return mInstalledThemes == null ? 0 : mInstalledThemes.size(); - } - - public void swapCursor(Cursor c) { - if (c != null && c.getCount() != 0) { - ArrayList previousOrder = mInstalledThemes == null ? null - : new ArrayList(mInstalledThemes); - mInstalledThemes = new ArrayList(c.getCount()); - mRepositionedFragments.clear(); - c.moveToPosition(-1); - while (c.moveToNext()) { - final int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - final String pkgName = c.getString(pkgIdx); - if (pkgName.equals(mAppliedBaseTheme)) { - final int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); - final int authorIdx = c.getColumnIndex(ThemesColumns.AUTHOR); - mAppliedThemeTitle = c.getString(titleIdx); - mAppliedThemeAuthor = c.getString(authorIdx); - } - mInstalledThemes.add(pkgName); - - // track any themes that may have changed position - if (previousOrder != null && previousOrder.contains(pkgName)) { - int index = previousOrder.indexOf(pkgName); - if (index != c.getPosition()) { - mRepositionedFragments.put(pkgName, c.getPosition()); - } - } else { - mRepositionedFragments.put(pkgName, c.getPosition()); - } - } - // check if any themes are no longer in the new list - if (previousOrder != null) { - for (String pkgName : previousOrder) { - if (!mInstalledThemes.contains(pkgName)) { - mRepositionedFragments.put(pkgName, POSITION_NONE); - } - } - } - } else { - mInstalledThemes = null; - } - } - - public void removeTheme(String pkgName) { - if (mInstalledThemes == null) return; - - if (mInstalledThemes.contains(pkgName)) { - final int count = mInstalledThemes.size(); - final int index = mInstalledThemes.indexOf(pkgName); - // reposition all the fragments after the one being removed - for (int i = index + 1; i < count; i++) { - mRepositionedFragments.put(mInstalledThemes.get(i), i - 1); - } - // Now remove this theme and add it to mRepositionedFragments with POSITION_NONE - mInstalledThemes.remove(pkgName); - mRepositionedFragments.put(pkgName, POSITION_NONE); - // now we can call notifyDataSetChanged() - notifyDataSetChanged(); - } - } - } - - private class TypefacePreloadTask extends AsyncTask { - - @Override - protected Object doInBackground(Object[] params) { - String[] projection = { ThemesColumns.PKG_NAME }; - String selection = ThemesColumns.MODIFIES_FONTS + "=?"; - String[] selectionArgs = { "1" }; - Cursor c = getContentResolver().query(ThemesColumns.CONTENT_URI, projection, selection, - selectionArgs, null); - if (c != null) { - while (c.moveToNext()) { - mTypefaceHelperCache.getHelperForTheme(ChooserActivity.this, c.getString(0)); - } - c.close(); - } - return null; - } - } - - /** - * Internal delete callback from the system - */ - class PackageDeleteObserver extends IPackageDeleteObserver.Stub { - public void packageDeleted(final String packageName, int returnCode) throws RemoteException { - if (returnCode == PackageManager.DELETE_SUCCEEDED) { - Log.d(TAG, "Delete succeeded"); - mHandler.post(new Runnable() { - @Override - public void run() { - mAdapter.removeTheme(packageName); - } - }); - } else { - Log.e(TAG, "Delete failed with returnCode " + returnCode); - } - } - } - - public void expandContentAndAnimateLockScreenCardIn() { - mHandler.post(new Runnable() { - @Override - public void run() { - expand(); - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f) - .setDuration(ANIMATE_CARDS_IN_DURATION)); - set.setStartDelay(mAnimateContentInDelay); - set.start(); - mContainer.setVisibility(View.VISIBLE); - getCurrentFragment().showLockScreenCard(); - } - }, ANIMATE_CARDS_IN_DURATION); - } - }); - } - - private void launchAppThemer() { - PreferenceUtils.setShowPerAppThemeNewTag(ChooserActivity.this, false); - Intent intent = new Intent(ChooserActivity.this, PerAppThemingWindow.class); - startService(intent); - finish(); - } - - private void requestSystemWindowPermission() { - Intent intent = new Intent (Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + getPackageName())); - startActivityForResult(intent, REQUEST_SYSTEM_WINDOW_PERMISSION); - } -} diff --git a/src/com/cyngn/theme/chooser/ComponentCardView.java b/src/com/cyngn/theme/chooser/ComponentCardView.java deleted file mode 100644 index 24eb58c..0000000 --- a/src/com/cyngn/theme/chooser/ComponentCardView.java +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.IntEvaluator; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewOverlay; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class ComponentCardView extends LinearLayout { - public static final int CARD_FADE_DURATION = 300; - - private static final float SEMI_OPAQUE_ALPHA = 0.4f; - - protected TextView mLabel; - protected View mEmptyView; - protected View mContentView; - - // Expanded Padding - int mExpandPadLeft; - int mExpandPadTop; - int mExpandPadRight; - int mExpandPadBottom; - - // The background drawable is returning an alpha of 0 regardless of what we set it to - // so this will help us keep track of what the drawable's alpha is at. - private int mBackgroundAlpha = 255; - - public ComponentCardView(Context context) { - this(context, null); - } - - public ComponentCardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ComponentCardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setEnabled(false); - } - - @Override - protected void onFinishInflate() { - mLabel = (TextView) findViewById(R.id.label); - - Resources r = getContext().getResources(); - mExpandPadLeft = - (int) r.getDimension(R.dimen.card_padding_left_right) + getPaddingLeft(); - mExpandPadTop = - (int) r.getDimension(R.dimen.card_padding_top) + getPaddingTop(); - mExpandPadRight = - (int) r.getDimension(R.dimen.card_padding_left_right) + getPaddingRight(); - mExpandPadBottom = - (int) r.getDimension(R.dimen.card_padding_bottom) + getPaddingBottom(); - mEmptyView = findViewById(R.id.empty); - mContentView = findViewById(R.id.content); - } - - public void expand(boolean showLabel) { - setEnabled(true); - TransitionDrawable bg = null; - if (getBackground() instanceof TransitionDrawable) { - bg = (TransitionDrawable) getBackground(); - } - if (bg != null) { - Rect paddingRect = new Rect(); - bg.getPadding(paddingRect); - } - - setPadding(mExpandPadLeft, 0, mExpandPadRight, mExpandPadBottom); - - if (mLabel != null) { - mLabel.setAlpha(showLabel ? 1f : 0f); - mLabel.setVisibility(View.VISIBLE); - } - } - - public void animateExpand() { - if (getBackground() instanceof TransitionDrawable) { - TransitionDrawable background = (TransitionDrawable) getBackground(); - if (mLabel != null) { - mLabel.setVisibility(View.VISIBLE); - mLabel.setAlpha(0f); - mLabel.animate().alpha(1f).setDuration(CARD_FADE_DURATION).start(); - } - background.startTransition(CARD_FADE_DURATION); - } - } - - public void collapse() { - setEnabled(false); - if (mLabel != null) { - mLabel.setVisibility(View.GONE); - } - setPadding(0, 0, 0, 0); - } - - public void animateFadeOut() { - if (mLabel != null) { - mLabel.animate().alpha(0f).setDuration(CARD_FADE_DURATION); - } - - if (getBackground() instanceof TransitionDrawable) { - TransitionDrawable background = (TransitionDrawable) getBackground(); - background.reverseTransition(CARD_FADE_DURATION); - } - } - - /** - * Animates the card background and the title to 20% opacity. - */ - public void animateCardFadeOut() { - this.animate().alpha(SEMI_OPAQUE_ALPHA).setDuration(CARD_FADE_DURATION); - } - - /** - * Animates the card background and the title back to full opacity. - */ - public void animateCardFadeIn() { - this.animate().alpha(1f).setDuration(CARD_FADE_DURATION); - } - - /** - * Animates a change in the content of the card - * @param v View in card to animate - * @param overlay Drawable to animate as a ViewOverlay - * @param duration Duration of animation - */ - public void animateContentChange(View v, final Drawable overlay, long duration) { - final ViewOverlay viewOverlay = this.getOverlay(); - viewOverlay.add(overlay); - final int x = getRelativeLeft(v); - final int y = getRelativeTop(v); - final int width = v.getWidth(); - final int height = v.getHeight(); - overlay.setBounds(x, y, x + v.getWidth(), y + v.getHeight()); - - final ValueAnimator overlayAnimator = ValueAnimator.ofFloat(1f, 0f); - overlayAnimator.setDuration(duration); - overlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (Float) animation.getAnimatedValue(); - overlay.setAlpha((int) (255 * value)); - int newWidth = (int) (value * width); - int newHeight = (int) (value * height); - int dw = (width - newWidth) / 2; - int dh = (height - newHeight) / 2; - overlay.setBounds(x + dw, y + dh, x + dw + newWidth, y + dh + newHeight); - } - }); - overlayAnimator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - // Clear out the ViewOverlay now that we are done animating - viewOverlay.clear(); - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }); - - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(v, "alpha", 0f, 1f)) - .with(ObjectAnimator.ofFloat(v, "scaleX", 0f, 1f)) - .with(ObjectAnimator.ofFloat(v, "scaleY", 0f, 1f)); - set.setDuration(duration); - - set.start(); - overlayAnimator.start(); - } - - public void setEmptyViewEnabled(boolean enabled) { - if (mEmptyView != null) { - mEmptyView.setVisibility(enabled ? View.VISIBLE : View.GONE); - } - if (mContentView != null) { - mContentView.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE); - } - } - - public boolean isShowingEmptyView() { - return mEmptyView != null && mEmptyView.getVisibility() == View.VISIBLE; - } - - private int getRelativeTop(View v) { - if (v.getParent() == this) { - return v.getTop(); - } else { - return v.getTop() + ((View) v.getParent()).getTop(); - } - } - - private int getRelativeLeft(View v) { - if (v.getParent() == this) { - return v.getLeft(); - } else { - return v.getLeft() + ((View) v.getParent()).getLeft(); - } - } -} diff --git a/src/com/cyngn/theme/chooser/ComponentSelector.java b/src/com/cyngn/theme/chooser/ComponentSelector.java deleted file mode 100644 index 5347250..0000000 --- a/src/com/cyngn/theme/chooser/ComponentSelector.java +++ /dev/null @@ -1,920 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.database.ContentObserver; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.support.v4.app.FragmentActivity; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.Animation; -import android.view.animation.Animation.AnimationListener; -import android.view.animation.AnimationUtils; -import android.widget.ComponentSelectorLinearLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import com.cyngn.theme.util.AudioUtils; -import com.cyngn.theme.util.CursorLoaderHelper; -import com.cyngn.theme.util.ThemedTypefaceHelper; -import com.cyngn.theme.util.TypefaceHelperCache; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.providers.ThemesContract; -import cyanogenmod.providers.ThemesContract.PreviewColumns; -import cyanogenmod.providers.ThemesContract.ThemesColumns; - -import java.util.Map; - -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; - -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_STATUS_BAR; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_FONT; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ICONS; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_WALLPAPER; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_NAVIGATION_BAR; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_LOCKSCREEN; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_STYLE; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_BOOT_ANIMATION; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_RINGTONE; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_NOTIFICATION; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ALARM; - -public class ComponentSelector extends LinearLayout - implements LoaderManager.LoaderCallbacks { - private static final String TAG = ComponentSelector.class.getSimpleName(); - - public static final boolean DEBUG_SELECTOR = false; - - public static final String EXTERNAL_WALLPAPER = "external"; - - public static final String MOD_LOCK = "mod_lock"; - - private static final int EXTRA_WALLPAPER_COMPONENTS = 2; - - private static final int EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS = 3; - - protected static final long DEFAULT_COMPONENT_ID = 0; - - private Context mContext; - private LayoutInflater mInflater; - private ComponentSelectorLinearLayout mContent; - private LinearLayout.LayoutParams mItemParams; - private LinearLayout.LayoutParams mSoundItemParams = - new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1.0f); - - private String mComponentType; - private int mBatteryStyle; - private int mItemsPerPage; - private String mAppliedComponentPkgName; - private String mSelectedComponentPkgName; - private long mSelectedComponentId; - - // animations for bringing selector in and out of view - private Animation mAnimateIn; - private Animation mAnimateOut; - - private OnItemClickedListener mListener; - - private OnOpenCloseListener mOpenCloseListener; - - private MediaPlayer mMediaPlayer; - private ImageView mCurrentPlayPause; - - private TypefaceHelperCache mTypefaceCache; - - private ThemesObserver mThemesObserver; - - public static final String IS_LIVE_LOCK_SCREEN_VIEW = "is_live_lock_screen_view"; - private View mPrevLockScreenView; - - public ComponentSelector(Context context, AttributeSet attrs) { - super(context, attrs); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ComponentSelector); - mItemsPerPage = a.getInt(R.styleable.ComponentSelector_itemsPerPage, - context.getResources().getInteger(R.integer.default_items_per_page)); - a.recycle(); - - mContext = context; - mInflater = LayoutInflater.from(context); - // TODO: Load from settings once available - mBatteryStyle = 0; /*Settings.System.getInt(context.getContentResolver(), - Settings.System.STATUS_BAR_BATTERY_STYLE, 0);*/ - - mAnimateIn = AnimationUtils.loadAnimation(mContext, - R.anim.component_selection_animate_in); - mAnimateOut = AnimationUtils.loadAnimation(mContext, - R.anim.component_selection_animate_out); - mAnimateIn.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - if (mOpenCloseListener != null) mOpenCloseListener.onSelectorOpened(); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - mAnimateOut.setAnimationListener(new AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - if (mOpenCloseListener != null) mOpenCloseListener.onSelectorClosing(); - } - - @Override - public void onAnimationEnd(Animation animation) { - setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - mMediaPlayer = new MediaPlayer(); - mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - if (mCurrentPlayPause != null) { - mCurrentPlayPause.setImageResource(R.drawable.media_sound_selector_preview); - mCurrentPlayPause = null; - } - } - }); - mTypefaceCache = TypefaceHelperCache.getInstance(); - mThemesObserver = new ThemesObserver(); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mContent = (ComponentSelectorLinearLayout) findViewById(R.id.content); - setEnabled(false); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mThemesObserver.register(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mThemesObserver.unregister(); - } - - @Override - protected void onWindowVisibilityChanged(int visibility) { - super.onWindowVisibilityChanged(visibility); - // Window visibility transitions as follows - // VISIBLE -> INVISIBLE -> GONE - // GONE -> INVISIBLE -> VISIBLE - if (visibility == View.GONE) { - mThemesObserver.unregister(); - } else if (visibility == View.VISIBLE) { - mThemesObserver.register(); - } - } - - public void resetComponentType() { - mComponentType = null; - } - - public void setComponentType(String component) { - setComponentType(component, null, DEFAULT_COMPONENT_ID); - } - - public void setComponentType(String component, String selectedPkgName) { - setComponentType(component, null, DEFAULT_COMPONENT_ID); - } - - public void setComponentType(String component, String selectedPkgName, - long selectedComponentId) { - mSelectedComponentPkgName = selectedPkgName; - mSelectedComponentId = selectedComponentId; - if (mComponentType == null || !mComponentType.equals(component)) { - mContent.removeAllViews(); - mComponentType = component; - ((FragmentActivity) mContext).getSupportLoaderManager().restartLoader( - getLoaderIdFromComponent(component), null, this); - } else if (mComponentType != null) { - int count = mContent.getChildCount(); - final Resources res = getResources(); - boolean highlightTitle; - Map selectedComponents = null; - if (mComponentType.equals(MODIFIES_LOCKSCREEN)) { - selectedComponents = ((ChooserActivity)mContext) - .getSelectedComponentsMap(); - } - for (int i = 0; i < count; i++) { - final View child = mContent.getChildAt(i); - final TextView tv = (TextView) child.findViewById(R.id.title); - // The child itself may have the tag, or in the case of sounds its the grandchild, - // either way the parent of the textview will have the pkgName in its tag - final View viewWithTag = (View) tv.getParent(); - final String pkgName = (String) viewWithTag.getTag(R.id.tag_key_package_name); - Long cmpntId = (Long) viewWithTag.getTag(R.id.tag_key_component_id); - if (cmpntId == null) { - cmpntId = DEFAULT_COMPONENT_ID; - } - Boolean isLLS = (Boolean) viewWithTag.getTag(R.id.tag_key_live_lock_screen); - highlightTitle = false; - if (selectedComponents != null && isLLS != null) { - if ((TextUtils.equals(selectedComponents.get(MODIFIES_LOCKSCREEN), pkgName) - && !isLLS) || (TextUtils.equals(selectedComponents.get( - MODIFIES_LIVE_LOCK_SCREEN), pkgName) && isLLS)) { - highlightTitle = true; - } - } else if (pkgName.equals(selectedPkgName) && cmpntId == selectedComponentId) { - highlightTitle = true; - } - if (highlightTitle) { - tv.setTextColor(res.getColor(R.color.component_selection_current_text_color)); - } else { - tv.setTextColor(res.getColor(android.R.color.white)); - } - } - } - } - - public String getComponentType() { - return mComponentType; - } - - public void setNumItemsPerPage(int itemsPerPage) { - if (mItemsPerPage != itemsPerPage) { - mItemsPerPage = itemsPerPage; - } - } - - public void setHeight(int height) { - ViewGroup.LayoutParams params = mContent.getLayoutParams(); - if (params.height != height) { - params.height = height; - mContent.setLayoutParams(params); - requestLayout(); - } - } - - public void show(String componentType, int itemsPerPage, int height) { - show(componentType, null, itemsPerPage, height); - } - - public void show(String componentType, String selectedPkgName, int itemsPerPage, int height) { - show(componentType, selectedPkgName, DEFAULT_COMPONENT_ID, itemsPerPage, height); - } - - public void show(String componentType, String selectedPkgName, long selectedComponentId, - int itemsPerPage, int height) { - setNumItemsPerPage(itemsPerPage); - setHeight(height); - setComponentType(componentType, selectedPkgName, selectedComponentId); - show(); - } - - public void show() { - if (getVisibility() == View.GONE) { - setEnabled(true); - setVisibility(View.VISIBLE); - startAnimation(mAnimateIn); - } - } - - public void hide() { - if (getVisibility() == View.VISIBLE && isEnabled()) { - setEnabled(false); - startAnimation(mAnimateOut); - } - if (mMediaPlayer != null && mMediaPlayer.isPlaying()) mMediaPlayer.stop(); - } - - private int getLoaderIdFromComponent(String component) { - if (MODIFIES_STATUS_BAR.equals(component)) { - return LOADER_ID_STATUS_BAR; - } - if (MODIFIES_NAVIGATION_BAR.equals(component)) { - return LOADER_ID_NAVIGATION_BAR; - } - if (MODIFIES_FONTS.equals(component)) { - return LOADER_ID_FONT; - } - if (MODIFIES_ICONS.equals(component)) { - return LOADER_ID_ICONS; - } - if (MODIFIES_OVERLAYS.equals(component)) { - return LOADER_ID_STYLE; - } - if (MODIFIES_LAUNCHER.equals(component)) { - return LOADER_ID_WALLPAPER; - } - if (MODIFIES_BOOT_ANIM.equals(component)) { - return LOADER_ID_BOOT_ANIMATION; - } - if (MODIFIES_RINGTONES.equals(component)) { - return LOADER_ID_RINGTONE; - } - if (MODIFIES_NOTIFICATIONS.equals(component)) { - return LOADER_ID_NOTIFICATION; - } - if (MODIFIES_ALARMS.equals(component)) { - return LOADER_ID_ALARM; - } - if (MODIFIES_LOCKSCREEN.equals(component)) { - return LOADER_ID_LOCKSCREEN; - } - return -1; - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - return CursorLoaderHelper.componentSelectorCursorLoader(mContext, id); - } - - @Override - public void onLoadFinished(Loader loader, final Cursor data) { - int currentLoaderId = loader.getId(); - int count = data.getCount(); - int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels; - final Resources res = getResources(); - int dividerPadding = res.getDimensionPixelSize(R.dimen.component_divider_padding_top); - int dividerHeight = res.getDimensionPixelSize(R.dimen.component_divider_height); - MatrixCursor lockScreenMatrixCursor = null; - - switch (currentLoaderId) { - case LOADER_ID_ALARM: - case LOADER_ID_NOTIFICATION: - case LOADER_ID_RINGTONE: - mItemParams = new LayoutParams(screenWidth, - ViewGroup.LayoutParams.MATCH_PARENT); - // Sounds are a special case where there are two items laid out - // vertically. This layout is added as a single item so we need to - // adjust the count by dividing by the number of items per page and - // rounding up so we include all items. - count = (int) Math.ceil((double)count / mItemsPerPage); - mContent.setShowDividers(LinearLayout.SHOW_DIVIDER_NONE); - break; - case LOADER_ID_BOOT_ANIMATION: - dividerPadding = res.getDimensionPixelSize( - R.dimen.component_divider_padding_top_bootani); - dividerHeight = res.getDimensionPixelSize(R.dimen.component_divider_height_bootani); - // fall through to default - default: - mItemParams = new LayoutParams(screenWidth / mItemsPerPage, - ViewGroup.LayoutParams.MATCH_PARENT); - if (currentLoaderId == LOADER_ID_WALLPAPER || - currentLoaderId == LOADER_ID_LOCKSCREEN) { - count += EXTRA_WALLPAPER_COMPONENTS; - } - - if (currentLoaderId == LOADER_ID_LOCKSCREEN) { - lockScreenMatrixCursor = splitLockScreenCursor(data); - if (lockScreenMatrixCursor != null) { - count = lockScreenMatrixCursor.getCount() + EXTRA_WALLPAPER_COMPONENTS; - } - } - - mContent.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); - break; - } - - mContent.setDividerPadding(dividerPadding); - mContent.setDividerHeight(dividerHeight); - - new LoadItemsTask().execute((lockScreenMatrixCursor != null) - ? lockScreenMatrixCursor : data, count); - } - - /* Some themes might contain a lock wallpaper AND a live lock screen. - * ThemesProvider will return one single row containing both thumbnail paths - * (ThemesProvider groups by theme_id) so we need to create a cursor on the - * fly to split that row into 2 to properly generate a view for each thumbnail - */ - private MatrixCursor splitLockScreenCursor(Cursor data) { - int lockWallPaperThumbnailIndx, llsThumbnailIndx, pkgIndx; - String lockWallPaperThumbnail, liveLockScreenThumbnail, pkgName; - MatrixCursor lockScreenMatrixCursor; - int needToSplitRowAt = -1; - - lockWallPaperThumbnailIndx = data.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL); - llsThumbnailIndx = data.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL); - pkgIndx = data.getColumnIndex(ThemesColumns.PKG_NAME); - - if (lockWallPaperThumbnailIndx < 0 || llsThumbnailIndx < 0 || pkgIndx < 0) { - //An invalid cursor was provided, we can't continue processing - Log.e(TAG, "Failed to process cursor due to missing columns"); - return null; - } - - //Let's find out if we really need to allocate a MatrixCursor. - //If we find at least one row with valid data in lock_wallpaper_thumbnail AND - //live_lock_screen_thumbnail it means we do. - data.moveToPosition(-1); - while (data.moveToNext()) { - lockWallPaperThumbnail = data.getString(lockWallPaperThumbnailIndx); - liveLockScreenThumbnail = data.getString(llsThumbnailIndx); - if (!TextUtils.isEmpty(lockWallPaperThumbnail) - && !TextUtils.isEmpty(liveLockScreenThumbnail)) { - needToSplitRowAt = data.getPosition(); - break; - } - } - - if (needToSplitRowAt == -1) return null; - - lockScreenMatrixCursor = new MatrixCursor(data.getColumnNames()); - //Clone all the *regular* rows up to needToSplitRowAt - for (int indx = 0; indx < needToSplitRowAt; indx++) { - data.moveToPosition(indx); - lockScreenMatrixCursor.addRow(CursorLoaderHelper.getRowFromCursor(data)); - } - if (needToSplitRowAt == 0) { - data.moveToPosition(-1); - } - while (data.moveToNext()) { - lockWallPaperThumbnail = data.getString(lockWallPaperThumbnailIndx); - liveLockScreenThumbnail = data.getString(llsThumbnailIndx); - if (!TextUtils.isEmpty(lockWallPaperThumbnail) - && !TextUtils.isEmpty(liveLockScreenThumbnail)) { - pkgName = data.getString(pkgIndx); - - MatrixCursor.RowBuilder lockWallpaperRow = lockScreenMatrixCursor.newRow(); - MatrixCursor.RowBuilder liveLockScreenRow = lockScreenMatrixCursor.newRow(); - - for (String col : data.getColumnNames()) { - if (TextUtils.equals(col, PreviewColumns.LOCK_WALLPAPER_THUMBNAIL)) { - lockWallpaperRow.add(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, - lockWallPaperThumbnail); - liveLockScreenRow.add(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, null); - } else if (TextUtils.equals(col, PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL)) { - lockWallpaperRow.add(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, null); - liveLockScreenRow.add(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - liveLockScreenThumbnail); - } else if (TextUtils.equals(col, MODIFIES_LIVE_LOCK_SCREEN)) { - lockWallpaperRow.add(MODIFIES_LIVE_LOCK_SCREEN, 0); - liveLockScreenRow.add(MODIFIES_LIVE_LOCK_SCREEN, 1); - } else { - int colIndx = data.getColumnIndex(col); - lockWallpaperRow.add(col, CursorLoaderHelper.getFieldValueFromRow(data, - colIndx)); - liveLockScreenRow.add(col, CursorLoaderHelper.getFieldValueFromRow(data, - colIndx)); - } - } - } else { - //This is a regular row, so just clone it - lockScreenMatrixCursor.addRow(CursorLoaderHelper.getRowFromCursor(data)); - } - } - return lockScreenMatrixCursor; - } - - @Override - public void onLoaderReset(Loader loader) { - } - - public void setOnItemClickedListener(OnItemClickedListener listener) { - mListener = listener; - } - - public void setOnOpenCloseListener(OnOpenCloseListener listener) { - mOpenCloseListener = listener; - } - - private View newView(Cursor cursor, int position, ViewGroup container) { - if (MODIFIES_STATUS_BAR.equals(mComponentType)) { - return newStatusBarView(cursor, container, position); - } - if (MODIFIES_NAVIGATION_BAR.equals(mComponentType)) { - return newNavBarView(cursor, container, position); - } - if (MODIFIES_FONTS.equals(mComponentType)) { - return newFontView(cursor, container, position); - } - if (MODIFIES_ICONS.equals(mComponentType)) { - return newIconView(cursor, container, position); - } - if (MODIFIES_OVERLAYS.equals(mComponentType)) { - return newStyleView(cursor, container, position); - } - if (MODIFIES_LAUNCHER.equals(mComponentType)) { - return newWallpapersView(cursor, container, position, - cursor.getColumnIndex(PreviewColumns.WALLPAPER_THUMBNAIL), false, - EXTRA_WALLPAPER_COMPONENTS); - } - if (MODIFIES_BOOT_ANIM.equals(mComponentType)) { - return newBootanimationView(cursor, container, position); - } - if (MODIFIES_RINGTONES.equals(mComponentType) || - MODIFIES_NOTIFICATIONS.equals(mComponentType) || - MODIFIES_ALARMS.equals(mComponentType)) { - return newSoundView(cursor, container, position, mComponentType); - } - if (MODIFIES_LOCKSCREEN.equals(mComponentType)) { - boolean isLiveLockScreen = false; - if (position >= EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS) { - cursor.moveToPosition(position - EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS); - int liveLockIndex = cursor.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN); - isLiveLockScreen = liveLockIndex >= 0 && - cursor.getInt(liveLockIndex) == 1; - } - int index = isLiveLockScreen - ? cursor.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL) - : cursor.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL); - return newWallpapersView(cursor, container, position, index, isLiveLockScreen, - EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS); - } - return null; - } - - private View newStatusBarView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.status_bar_component_selection_item, - parent, false); - int wifiIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_ICON); - int signalIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_SIGNAL_ICON); - int bluetoothIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BLUETOOTH_ICON); - int batteryIndex = cursor.getColumnIndex(Utils.getBatteryIndex(mBatteryStyle)); - int backgroundIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.slot1)).setImageBitmap( - Utils.loadBitmapBlob(cursor, wifiIndex)); - ((ImageView) v.findViewById(R.id.slot2)).setImageBitmap( - Utils.loadBitmapBlob(cursor, signalIndex)); - ((ImageView) v.findViewById(R.id.slot3)).setImageBitmap( - Utils.loadBitmapBlob(cursor, bluetoothIndex)); - ((ImageView) v.findViewById(R.id.slot4)).setImageBitmap( - Utils.loadBitmapBlob(cursor, batteryIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.findViewById(R.id.container).setBackground( - new BitmapDrawable(Utils.loadBitmapBlob(cursor, backgroundIndex))); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newNavBarView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.navigation_bar_component_selection_item, parent, - false); - int backIndex = cursor.getColumnIndex(PreviewColumns.NAVBAR_BACK_BUTTON); - int backgroundIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.back)).setImageBitmap( - Utils.loadBitmapBlob(cursor, backIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.findViewById(R.id.container).setBackground( - new BitmapDrawable(Utils.loadBitmapBlob(cursor, backgroundIndex))); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newFontView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.font_component_selection_item, parent, false); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - TextView preview = (TextView) v.findViewById(R.id.text_preview); - String pkgName = cursor.getString(pkgNameIndex); - - ThemedTypefaceHelper helper = mTypefaceCache.getHelperForTheme(mContext, pkgName); - Typeface typefaceNormal = helper.getTypeface(Typeface.NORMAL); - preview.setTypeface(typefaceNormal); - - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newIconView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.icon_component_selection_item, parent, - false); - int iconIndex = cursor.getColumnIndex(PreviewColumns.ICON_PREVIEW_1); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.icon)).setImageBitmap( - Utils.loadBitmapBlob(cursor, iconIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newStyleView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.icon_component_selection_item, parent, - false); - int styleIndex = cursor.getColumnIndex(PreviewColumns.STYLE_THUMBNAIL); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.icon)).setImageBitmap( - Utils.loadBitmapBlob(cursor, styleIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newWallpapersView(Cursor cursor, ViewGroup parent, int position, - int wallpaperIndex, boolean isLiveLockScreen, int numExtraComponents) { - View v = mInflater.inflate(R.layout.wallpaper_component_selection_item, parent, - false); - ImageView iv = (ImageView) v.findViewById(R.id.icon); - if (position == 0) { - iv.setImageResource(R.drawable.img_wallpaper_none); - v.setTag(R.id.tag_key_package_name, ""); - ((TextView) v.findViewById(R.id.title)).setText(R.string.wallpaper_none_title); - } else if (position == 1) { - iv.setImageResource(R.drawable.img_wallpaper_external); - v.setTag(R.id.tag_key_package_name, EXTERNAL_WALLPAPER); - ((TextView) v.findViewById(R.id.title)) - .setText(R.string.wallpaper_external_title); - } else if (numExtraComponents == EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS && position == 2) { - // TODO: update drawable once the asset is provided by design - iv.setImageResource(android.R.drawable.ic_lock_lock); - v.setTag(R.id.tag_key_package_name, MOD_LOCK); - ((TextView) v.findViewById(R.id.title)) - .setText(R.string.mod_lock_title); - } else { - cursor.moveToPosition(position - numExtraComponents); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - int cmpntIdIndex = cursor.getColumnIndex(PreviewColumns.COMPONENT_ID); - long cmpntId = (cmpntIdIndex >= 0) ? - cursor.getLong(cmpntIdIndex) : DEFAULT_COMPONENT_ID; - iv.setImageBitmap( - Utils.loadBitmapBlob(cursor, wallpaperIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setTag(R.id.tag_key_component_id, cmpntId); - v.setTag(R.id.tag_key_live_lock_screen, isLiveLockScreen); - v.findViewById(R.id.live_lock_screen_badge) - .setVisibility(isLiveLockScreen ? View.VISIBLE : View.GONE); - } - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newBootanimationView(Cursor cursor, ViewGroup parent, int position) { - cursor.moveToPosition(position); - View v = mInflater.inflate(R.layout.bootani_component_selection_item, parent, - false); - int wallpaperIndex = cursor.getColumnIndex(PreviewColumns.BOOTANIMATION_THUMBNAIL); - int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - ((ImageView) v.findViewById(R.id.preview)).setImageBitmap( - Utils.loadBitmapBlob(cursor, wallpaperIndex)); - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - return v; - } - - private View newSoundView(Cursor cursor, ViewGroup parent, int position, - final String component) { - LinearLayout container = (LinearLayout) mInflater.inflate( - R.layout.component_selection_sounds_pager_item, parent, false); - container.setWeightSum(mItemsPerPage); - for (int i = 0; i < mItemsPerPage; i++) { - int index = position * mItemsPerPage + i; - if (cursor.getCount() <= index) continue; - cursor.moveToPosition(index); - View v = mInflater.inflate(R.layout.sound_component_selection_item, parent, - false); - final int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - - setTitle(((TextView) v.findViewById(R.id.title)), cursor); - v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); - v.setOnClickListener(mItemClickListener); - container.addView(v, mSoundItemParams); - final View playButton = v.findViewById(R.id.play_button); - playButton.setTag(cursor.getString(pkgNameIndex)); - playButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - int type; - String pkgName = (String) v.getTag(); - if (component.equals(MODIFIES_RINGTONES)) { - type = RingtoneManager.TYPE_RINGTONE; - } else if (component.equals(MODIFIES_NOTIFICATIONS)) { - type = RingtoneManager.TYPE_NOTIFICATION; - } else { - type = RingtoneManager.TYPE_ALARM; - } - boolean shouldStop = playButton == mCurrentPlayPause; - try { - if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { - mMediaPlayer.stop(); - if (mCurrentPlayPause != null) { - mCurrentPlayPause.setImageResource( - R.drawable.media_sound_selector_preview); - } - mCurrentPlayPause = null; - } - if (mCurrentPlayPause != playButton && !shouldStop) { - AudioUtils.loadThemeAudible(mContext, type, pkgName, - mMediaPlayer); - mMediaPlayer.start(); - mCurrentPlayPause = (ImageView) playButton; - mCurrentPlayPause.setImageResource( - R.drawable.media_sound_selector_stop); - } - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Unable to play preview sound", e); - } - } - }); - } - - return container; - } - - private class LoadItemsTask extends AsyncTask { - - @Override - protected Void doInBackground(Object... params) { - Cursor c = (Cursor) params[0]; - int count = (Integer) params[1]; - for (int i = 0; i < count && !isCancelled(); i++) { - final View v = newView(c, i, mContent); - mContent.post(new Runnable() { - @Override - public void run() { - mContent.addView(v, mItemParams); - } - }); - } - - if (c instanceof MatrixCursor) { - c.close(); - } - - // destroy the loader now that we are done with it - ComponentSelector.this.post(new Runnable() { - @Override - public void run() { - ((FragmentActivity)mContext).getSupportLoaderManager().destroyLoader( - getLoaderIdFromComponent(mComponentType)); - } - }); - return null; - } - } - - private void setTitle(TextView titleView, Cursor cursor) { - String pkgName = cursor.getString(cursor.getColumnIndex(ThemesColumns.PKG_NAME)); - int cmpntIdIndex = cursor.getColumnIndex(PreviewColumns.COMPONENT_ID); - long cmpntId = DEFAULT_COMPONENT_ID; - if (cmpntIdIndex >= 0) { - cmpntId = cursor.getLong(cmpntIdIndex); - } - if (Utils.getDefaultThemePackageName(mContext).equals(pkgName)) { - titleView.setText(mContext.getString(R.string.default_tag_text)); - titleView.setTypeface(titleView.getTypeface(), Typeface.BOLD); - } else { - titleView.setText(cursor.getString(cursor.getColumnIndex(ThemesColumns.TITLE))); - } - boolean highlightTitle = false; - if (mComponentType.equals(MODIFIES_LOCKSCREEN)) { - Map selectedComponents = ((ChooserActivity)mContext) - .getSelectedComponentsMap(); - int isLLS = cursor.getInt(cursor.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN)); - if ((TextUtils.equals(selectedComponents.get(MODIFIES_LOCKSCREEN), pkgName) - && isLLS == 0) || (TextUtils.equals( - selectedComponents.get(MODIFIES_LIVE_LOCK_SCREEN), pkgName) && isLLS == 1)) { - highlightTitle = true; - } - } else if (pkgName.equals(mSelectedComponentPkgName) && cmpntId == mSelectedComponentId) { - highlightTitle = true; - } - if (highlightTitle) { - titleView.setTextColor(getResources().getColor( - R.color.component_selection_current_text_color)); - } - } - - private OnClickListener mItemClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - long cmpntId = DEFAULT_COMPONENT_ID; - String pkgName = (String) v.getTag(R.id.tag_key_package_name); - Long cmpntIdTag = (Long) v.getTag(R.id.tag_key_component_id); - if (cmpntIdTag != null) { - cmpntId = cmpntIdTag; - } - Boolean isLiveLock = (Boolean) v.getTag(R.id.tag_key_live_lock_screen); - boolean isSamePkgButDifferentLockScreen = false; - Bundle params = null; - if (isLiveLock != null) { - params = new Bundle(); - params.putBoolean(IS_LIVE_LOCK_SCREEN_VIEW, isLiveLock); - - if (pkgName.equals(mSelectedComponentPkgName) && v != mPrevLockScreenView) { - isSamePkgButDifferentLockScreen = true; - } - mPrevLockScreenView = v; - } - if (DEBUG_SELECTOR) Toast.makeText(mContext, pkgName, Toast.LENGTH_SHORT).show(); - if (mListener != null && (isSamePkgButDifferentLockScreen || - !pkgName.equals(mSelectedComponentPkgName) || - pkgName.equals(EXTERNAL_WALLPAPER) || cmpntId != mSelectedComponentId)) { - mSelectedComponentPkgName = pkgName; - mSelectedComponentId = cmpntId; - mListener.onItemClicked(pkgName, cmpntId, params); - final int count = mContent.getChildCount(); - final Resources res = getResources(); - for (int i = 0; i < count; i++) { - final View child = mContent.getChildAt(i); - final TextView tv = (TextView) child.findViewById(R.id.title); - if (tv != null) { - if (child == v) { - tv.setTextColor( - res.getColor(R.color.component_selection_current_text_color)); - } else { - tv.setTextColor(res.getColor(android.R.color.white)); - } - } - } - } - } - }; - - private class ThemesObserver extends ContentObserver { - public ThemesObserver() { - super(null); - } - - public void register() { - mContext.getContentResolver().registerContentObserver( - ThemesColumns.CONTENT_URI, false, this); - } - - public void unregister() { - mContext.getContentResolver().unregisterContentObserver(this); - } - - @Override - public void onChange(boolean selfChange) { - // reload items by calling setComponentType() - if (mComponentType != null) { - final String componentType = mComponentType; - mComponentType = null; - mContent.post(new Runnable() { - @Override - public void run() { - setComponentType(componentType, mSelectedComponentPkgName); - } - }); - } - } - } - - public interface OnItemClickedListener { - public void onItemClicked(String pkgName, long componentId, Bundle params); - } - - public interface OnOpenCloseListener { - public void onSelectorOpened(); - public void onSelectorClosed(); - public void onSelectorClosing(); - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/chooser/IconTransitionDrawable.java b/src/com/cyngn/theme/chooser/IconTransitionDrawable.java deleted file mode 100644 index 8079fc2..0000000 --- a/src/com/cyngn/theme/chooser/IconTransitionDrawable.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * Copyright (C) 2014 The Cyanogen, Inc - * - * 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.cyngn.theme.chooser; - -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.LayerDrawable; -import android.os.SystemClock; - -/** - * An extension of LayerDrawables that is intended to cross-fade between - * the first and second layer. To start the transition, call {@link #startTransition(int)}. To - * display just the first layer, call {@link #resetTransition()}. - *

- * It can be defined in an XML file with the <transition> element. - * Each Drawable in the transition is defined in a nested <item>. For more - * information, see the guide to Drawable Resources.

- * - * @attr ref android.R.styleable#LayerDrawableItem_left - * @attr ref android.R.styleable#LayerDrawableItem_top - * @attr ref android.R.styleable#LayerDrawableItem_right - * @attr ref android.R.styleable#LayerDrawableItem_bottom - * @attr ref android.R.styleable#LayerDrawableItem_drawable - * @attr ref android.R.styleable#LayerDrawableItem_id - * - */ -public class IconTransitionDrawable extends LayerDrawable { - - /** - * A transition is about to start. - */ - private static final int TRANSITION_STARTING = 0; - - /** - * The transition has started and the animation is in progress - */ - private static final int TRANSITION_RUNNING = 1; - - /** - * No transition will be applied - */ - private static final int TRANSITION_NONE = 2; - - /** - * The current state of the transition. One of {@link #TRANSITION_STARTING}, - * {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE} - */ - private int mTransitionState = TRANSITION_NONE; - - private long mStartTimeMillis; - private int mFrom; - private int mTo; - private int mDuration; - private int mAlpha = 0; - private float mFromScale; - private float mToScale; - - /** - * Create a new transition drawable with the specified list of layers. At least - * 2 layers are required for this drawable to work properly. - */ - public IconTransitionDrawable(Drawable[] layers) { - super(layers); - } - - /** - * Begin the second layer on top of the first layer. - * - * @param durationMillis The length of the transition in milliseconds - */ - public void startTransition(int durationMillis) { - mFrom = 0; - mTo = 255; - mAlpha = 0; - mFromScale = 0f; - mToScale = 1.0f; - mDuration = durationMillis; - mTransitionState = TRANSITION_STARTING; - invalidateSelf(); - } - - /** - * Show only the first layer. - */ - public void resetTransition() { - mAlpha = 0; - mTransitionState = TRANSITION_NONE; - invalidateSelf(); - } - - @Override - public void draw(Canvas canvas) { - boolean done = true; - float scale = 0f; - - switch (mTransitionState) { - case TRANSITION_STARTING: - mStartTimeMillis = SystemClock.uptimeMillis(); - done = false; - mTransitionState = TRANSITION_RUNNING; - break; - - case TRANSITION_RUNNING: - if (mStartTimeMillis >= 0) { - float normalized = (float) - (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration; - done = normalized >= 1.0f; - normalized = Math.min(normalized, 1.0f); - mAlpha = (int) (mFrom + (mTo - mFrom) * normalized); - scale = mFromScale + (mToScale - mFromScale) * normalized; - } - break; - } - - final int alpha = mAlpha; - - if (done) { - // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw - // the appropriate drawable[s] and return - if (alpha == 0) { - getDrawable(0).draw(canvas); - } - if (alpha == 0xFF) { - getDrawable(1).draw(canvas); - - } - return; - } - - Drawable d; - d = getDrawable(0); - d.setAlpha(255 - alpha); - int cx = getIntrinsicWidth() / 2; - int cy = getIntrinsicHeight() / 2; - canvas.save(); - canvas.scale(1.0f - scale, 1.0f - scale, cx, cy); - d.draw(canvas); - canvas.restore(); - d.setAlpha(0xFF); - - if (alpha > 0) { - d = getDrawable(1); - d.setAlpha(alpha); - canvas.save(); - canvas.scale(scale, scale, cx, cy); - d.draw(canvas); - canvas.restore(); - d.setAlpha(0xFF); - } - - if (!done) { - invalidateSelf(); - } - } -} diff --git a/src/com/cyngn/theme/chooser/MyThemeFragment.java b/src/com/cyngn/theme/chooser/MyThemeFragment.java deleted file mode 100644 index 8306e55..0000000 --- a/src/com/cyngn/theme/chooser/MyThemeFragment.java +++ /dev/null @@ -1,693 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.app.WallpaperManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.res.Resources; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.media.MediaPlayer; -import android.media.Ringtone; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.content.Loader; -import android.util.Log; -import android.util.MutableLong; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MenuItem; -import android.view.SurfaceView; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import com.cyngn.theme.util.AudioUtils; -import com.cyngn.theme.util.CursorLoaderHelper; -import com.cyngn.theme.util.PreferenceUtils; -import com.cyngn.theme.util.ThemedTypefaceHelper; -import com.cyngn.theme.util.TypefaceHelperCache; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.providers.ThemesContract; -import cyanogenmod.providers.ThemesContract.PreviewColumns; -import cyanogenmod.providers.ThemesContract.ThemesColumns; -import cyanogenmod.themes.ThemeChangeRequest; -import cyanogenmod.themes.ThemeChangeRequest.RequestType; -import cyanogenmod.themes.ThemeManager; - -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ALL; - -public class MyThemeFragment extends ThemeFragment { - private static final String TAG = MyThemeFragment.class.getSimpleName(); - - private static final String ARG_BASE_THEME_PACKAGE_NAME = "baseThemePkgName"; - private static final String ARG_BASE_THEME_NAME = "baseThemeName"; - private static final String ARG_BASE_THEME_AUTHOR = "baseThemeAuthor"; - - private String mBaseThemeName; - private String mBaseThemeAuthor; - - private SurfaceView mSurfaceView; - - static MyThemeFragment newInstance(String baseThemePkgName, String baseThemeName, - String baseThemeAuthor, boolean skipLoadingAnim, - boolean animateToLockScreenCard) { - MyThemeFragment f = new MyThemeFragment(); - Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, CURRENTLY_APPLIED_THEME); - args.putString(ARG_BASE_THEME_PACKAGE_NAME, baseThemePkgName); - args.putString(ARG_BASE_THEME_NAME, baseThemeName); - args.putString(ARG_BASE_THEME_AUTHOR, baseThemeAuthor); - args.putBoolean(ARG_SKIP_LOADING_ANIM, skipLoadingAnim); - args.putBoolean(ARG_ANIMATE_TO_LOCK_SCREEN_CARD, animateToLockScreenCard); - f.setArguments(args); - return f; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Context context = getActivity(); - ThemedTypefaceHelper helper = sTypefaceHelperCache.getHelperForTheme(context, - getAppliedFontPackageName()); - mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); - mBaseThemePkgName = getArguments().getString(ARG_BASE_THEME_PACKAGE_NAME); - mBaseThemeName = getArguments().getString(ARG_BASE_THEME_NAME); - mBaseThemeAuthor = getArguments().getString(ARG_BASE_THEME_AUTHOR); - mShowLockScreenSelectorAfterContentLoaded = getArguments().getBoolean( - ARG_ANIMATE_TO_LOCK_SCREEN_CARD); - mSurfaceView = createSurfaceView(); - populateBaseThemeSupportedComponents(mBaseThemePkgName); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = super.onCreateView(inflater, container, savedInstanceState); - mThemeTagLayout.setAppliedTagEnabled(true); - if (mBaseThemePkgName.equals(Utils.getDefaultThemePackageName(getActivity()))) { - mThemeTagLayout.setDefaultTagEnabled(true); - } - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { - mThemeTagLayout.setUpdatedTagEnabled(true); - } - mDelete.setVisibility(View.GONE); - setCustomized(isThemeCustomized()); - return v; - } - - @Override - public void onResume() { - super.onResume(); - if (!mExpanded && getLoaderManager().getLoader(0) != null) { - getLoaderManager().restartLoader(0, null, this); - } - - IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); - getActivity().registerReceiver(mWallpaperChangeReceiver, filter); - } - - @Override - public void onPause() { - getActivity().unregisterReceiver(mWallpaperChangeReceiver); - super.onPause(); - } - - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - if (mThemeTagLayout == null) return; - - if (!isVisibleToUser) { - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { - mThemeTagLayout.setUpdatedTagEnabled(true); - } - } else { - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { - PreferenceUtils.removeUpdatedTheme(getActivity(), mBaseThemePkgName); - } - } - } - - @Override - protected boolean onPopupMenuItemClick(MenuItem item) { - switch(item.getItemId()) { - case R.id.menu_reset: - resetTheme(); - return true; - } - - return super.onPopupMenuItemClick(item); - } - - @Override - public void collapse(boolean applyTheme) { - super.collapse(applyTheme); - if (mSurfaceView != null) mSurfaceView.setVisibility(View.VISIBLE); - } - - @Override - public void expand() { - super.expand(); - if (mSurfaceView != null && mShadowFrame.indexOfChild(mSurfaceView) >= 0) { - mSurfaceView.setVisibility(View.GONE); - mWallpaper.setVisibility(View.INVISIBLE); - } - } - - @Override - public void performClick(boolean clickedOnContent) { - if (clickedOnContent) { - showCustomizeResetLayout(); - } else { - if (isShowingCustomizeResetLayout()) { - hideCustomizeResetLayout(); - } else { - super.performClick(clickedOnContent); - } - } - } - - @Override - public void setCurrentTheme(Map currentTheme, - MutableLong currentWallpaperComponentId) { - super.setCurrentTheme(currentTheme, currentWallpaperComponentId); - for (String key : currentTheme.keySet()) { - mSelectedComponentsMap.put(key, currentTheme.get(key)); - } - mSelectedWallpaperComponentId = currentWallpaperComponentId.value; - } - - @Override - public boolean componentsChanged() { - // If an external wallpaper/ls are set then something changed! - if (mExternalWallpaperUri != null || mExternalLockscreenUri != null) return true; - - for (String key : mSelectedComponentsMap.keySet()) { - String current = mCurrentTheme.get(key); - if (current == null || !current.equals(mSelectedComponentsMap.get(key))) { - return true; - } - if (ThemesColumns.MODIFIES_LAUNCHER.equals(key) && - mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { - return true; - } - } - return false; - } - - @Override - protected void applyThemeWhenPopulated(String pkgName, List components) { - super.applyThemeWhenPopulated(pkgName, components); - populateComponentsToApply(pkgName, components); - } - - private void populateComponentsToApply(String pkgName, List components) { - String selection = ThemesColumns.PKG_NAME + "=?"; - String[] selectionArgs = { pkgName }; - Cursor c = getActivity().getContentResolver().query(ThemesColumns.CONTENT_URI, - null, selection, selectionArgs, null); - if (c != null) { - if (c.getCount() > 0 && c.moveToFirst()) { - mSelectedComponentsMap.clear(); - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_ALARMS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_ALARMS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_BOOT_ANIM)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_BOOT_ANIM, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_FONTS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_FONTS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_ICONS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_ICONS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LAUNCHER)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LOCKSCREEN)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LOCKSCREEN, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_NAVIGATION_BAR)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_NAVIGATION_BAR, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_NOTIFICATIONS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_NOTIFICATIONS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_OVERLAYS)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_OVERLAYS, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_RINGTONES)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_RINGTONES, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_STATUS_BAR)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_STATUS_BAR, pkgName); - } - if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) == 1) { - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, pkgName); - } - } - c.close(); - } - - // strip out any components that are not in the components list - if (components != null) { - Iterator> iterator = - mSelectedComponentsMap.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - if (!components.contains(entry.getKey())) { - iterator.remove(); - } - } - } - } - - private void loadComponentsToApply() { - for (String component : mSelectedComponentsMap.keySet()) { - loadComponentFromPackage(mSelectedComponentsMap.get(component), component, - mSelectedWallpaperComponentId); - } - } - - private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - // only update if we are the current visible fragment or if there is no theme - // being applied. - ThemeManager tm = getThemeManager(); - if (!tm.isThemeApplying() || getUserVisibleHint()) { - final WallpaperManager wm = WallpaperManager.getInstance(context); - if (wm.getWallpaperInfo() != null) { - addSurfaceView(mSurfaceView); - } else { - removeSurfaceView(mSurfaceView); - } - - Drawable wp = context == null ? null : wm.getDrawable(); - if (wp != null) { - mWallpaper.setImageDrawable(wp); - mWallpaperCard.setWallpaper(wp); - } - } - } - }; - - private void setCustomized(boolean customized) { - mReset.setVisibility(customized ? View.VISIBLE : View.GONE); - mThemeTagLayout.setCustomizedTagEnabled(customized); - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - switch (id) { - case LOADER_ID_ALL: - if (args != null) { - String pkgName = args.getString(ARG_PACKAGE_NAME); - if (pkgName != null) { - return super.onCreateLoader(id, args); - } - } - return CursorLoaderHelper.myThemeFragmentCursorLoader(getActivity(), id); - default: - // Only LOADER_ID_ALL differs for MyThemeFragment - return super.onCreateLoader(id, args); - } - } - - @Override - public void onLoadFinished(Loader loader, Cursor c) { - super.onLoadFinished(loader, c); - // if the theme is resetting, we need to apply these changes now that the supported - // theme components have been properly set. - if (loader.getId() == LOADER_ID_ALL) { - if (mThemeResetting) { - applyTheme(); - } else if (mApplyThemeOnPopulated) { - loadComponentsToApply(); - applyTheme(); - } else if (mSelectedComponentsMap.size() == 0) { - //Re-populates selected components with current theme. Why? - //We got here because the cursor was reloaded after the user pressed back and no - //changes were applied, causing the selected components map to be wiped out - mSelectedComponentsMap.putAll(mCurrentTheme); - } - } - } - - @Override - protected Map fillMissingComponentsWithDefault( - Map originalMap) { - // Only the ThemeFragment should be altering this, for the MyThemeFragment this is not - // desirable as it changes components the user did not even touch. - return originalMap; - } - - @Override - protected ThemeChangeRequest getThemeChangeRequestForComponents( - Map componentMap) { - return getThemeChangeRequestForComponents(componentMap, RequestType.USER_REQUEST_MIXNMATCH); - } - - @Override - protected Map getComponentsToApply() { - Map componentsToApply = mThemeResetting - ? getEmptyComponentsMap() - : new HashMap(); - if (mThemeResetting) { - final String pkgName = getThemePackageName(); - for (String component : mBaseThemeSupportedComponents) { - componentsToApply.put(component, pkgName); - } - } else { - // Only apply components that actually changed - for (String component : mSelectedComponentsMap.keySet()) { - String currentPkg = mCurrentTheme.get(component); - String selectedPkg = mSelectedComponentsMap.get(component); - if (currentPkg == null || mThemeResetting || !currentPkg.equals(selectedPkg) || - mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { - componentsToApply.put(component, selectedPkg); - } - } - if (mExternalLockscreenUri != null) { - if (mCurrentTheme.containsKey(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) { - componentsToApply.put(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); - } - if (mCurrentTheme.containsKey(ThemesColumns.MODIFIES_LOCKSCREEN)) { - componentsToApply.put(ThemesColumns.MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); - } - } - } - return componentsToApply; - } - - @Override - protected void populateSupportedComponents(Cursor c) { - } - - @Override - protected Boolean shouldShowComponentCard(String component) { - return true; - } - - @Override - protected void loadTitle(Cursor c) { - mTitle.setText(mBaseThemeName); - mAuthor.setText(mBaseThemeAuthor); - } - - @Override - protected void loadWallpaper(Cursor c, boolean animate) { - mExternalWallpaperUri = null; - int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - if (pkgNameIdx > -1) { - super.loadWallpaper(c, animate); - return; - } - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mWallpaperCard, true); - } - - int wpIdx = c.getColumnIndex(PreviewColumns.WALLPAPER_PREVIEW); - final Resources res = getResources(); - final Context context = getActivity(); - final WallpaperManager wm = WallpaperManager.getInstance(context); - if (wm.getWallpaperInfo() != null) { - addSurfaceView(mSurfaceView); - } else { - removeSurfaceView(mSurfaceView); - } - - Drawable wp = context == null ? null : wm.getDrawable(); - if (wp == null) { - Bitmap bmp = Utils.loadBitmapBlob(c, wpIdx); - if (bmp != null) wp = new BitmapDrawable(res, bmp); - } - if (wp != null) { - mWallpaper.setImageDrawable(wp); - mWallpaperCard.setWallpaper(wp); - setCardTitle(mWallpaperCard, mCurrentTheme.get(ThemesColumns.MODIFIES_LAUNCHER), - getString(R.string.wallpaper_label)); - } else { - mWallpaperCard.clearWallpaper(); - mWallpaperCard.setEmptyViewEnabled(true); - setAddComponentTitle(mWallpaperCard, getString(R.string.wallpaper_label)); - } - - if (animate) { - animateContentChange(R.id.wallpaper_card, mWallpaperCard, overlay); - } - } - - @Override - protected void loadLockScreen(Cursor c, boolean animate) { - mExternalLockscreenUri = null; - int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - if (pkgNameIdx > -1) { - super.loadLockScreen(c, animate); - return; - } - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mLockScreenCard, true); - } - - //If the current theme includes a lock wallpaper, the WallpaperMgr will - //return a valid Drawable we can display in the card. However, if the user - //picked a LLS, we need to get the path from the provider and manually load the bitmap - int wpIdx = c.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW); - Drawable wp = null; - if (wpIdx >= 0) { - final Resources res = getResources(); - Bitmap bmp = Utils.loadBitmapBlob(c, wpIdx); - if (bmp != null) wp = new BitmapDrawable(res, bmp); - } else { - final Context context = getActivity(); - wp = context == null ? null : - WallpaperManager.getInstance(context).getFastKeyguardDrawable(); - } - if (wp != null) { - mLockScreenCard.setWallpaper(wp); - } else if (!mSelectedComponentsMap.containsKey(ThemesColumns.MODIFIES_LOCKSCREEN)) { - mLockScreenCard.clearWallpaper(); - mLockScreenCard.setEmptyViewEnabled(true); - setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); - } - - if (animate) { - animateContentChange(R.id.lockscreen_card, mLockScreenCard, overlay); - } - } - - @Override - protected void loadFont(Cursor c, boolean animate) { - int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - if (pkgNameIdx > -1) { - super.loadFont(c, animate); - return; - } - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mFontPreview, true); - } - setCardTitle(mFontCard, mCurrentTheme.get(ThemesColumns.MODIFIES_FONTS), - getString(R.string.font_label)); - - TypefaceHelperCache cache = TypefaceHelperCache.getInstance(); - ThemedTypefaceHelper helper = cache.getHelperForTheme(getActivity(), - getAppliedFontPackageName()); - mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); - mFontPreview.setTypeface(mTypefaceNormal); - if (animate) { - animateContentChange(R.id.font_preview_container, mFontPreview, overlay); - } - } - - @Override - protected void loadAudible(int type, Cursor c, boolean animate) { - int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); - if (pkgNameIdx > -1) { - super.loadAudible(type, c, animate); - return; - } - ComponentCardView audibleContainer = null; - ImageView playPause = null; - String modsComponent = ""; - switch (type) { - case RingtoneManager.TYPE_RINGTONE: - audibleContainer = mRingtoneCard; - playPause = mRingtonePlayPause; - modsComponent = ThemesColumns.MODIFIES_RINGTONES; - break; - case RingtoneManager.TYPE_NOTIFICATION: - audibleContainer = mNotificationCard; - playPause = mNotificationPlayPause; - modsComponent = ThemesColumns.MODIFIES_NOTIFICATIONS; - break; - case RingtoneManager.TYPE_ALARM: - audibleContainer = mAlarmCard; - playPause = mAlarmPlayPause; - modsComponent = ThemesColumns.MODIFIES_ALARMS; - break; - } - if (audibleContainer == null) return; - - if (playPause == null) { - playPause = - (ImageView) audibleContainer.findViewById(R.id.play_pause); - } - TextView title = (TextView) audibleContainer.findViewById(R.id.audible_name); - MediaPlayer mp = mMediaPlayers.get(playPause); - if (mp == null) { - mp = new MediaPlayer(); - } - - final Context context = getActivity(); - Ringtone ringtone = null; - try { - Uri ringtoneUri = AudioUtils.loadDefaultAudible(context, type, mp); - if (ringtoneUri != null) ringtone = RingtoneManager.getRingtone(context, ringtoneUri); - } catch (IOException e) { - Log.w(TAG, "Unable to load default sound ", e); - } - - if (ringtone != null) { - title.setText(ringtone.getTitle(context)); - setCardTitle(audibleContainer, mCurrentTheme.get(modsComponent), - getAudibleLabel(type)); - } else { - title.setText(getString(R.string.audible_title_none)); - setAddComponentTitle(audibleContainer, getAudibleLabel(type)); - playPause.setVisibility(View.INVISIBLE); - audibleContainer.setEmptyViewEnabled(true); - } - - playPause.setTag(mp); - mMediaPlayers.put(playPause, mp); - playPause.setOnClickListener(mPlayPauseClickListener); - mp.setOnCompletionListener(mPlayCompletionListener); - } - - @Override - protected void loadStatusBar(Cursor c, boolean animate) { - super.loadStatusBar(c, animate); - setCardTitle(mStatusBarCard, mCurrentTheme.get(ThemesColumns.MODIFIES_STATUS_BAR), - getString(R.string.statusbar_label)); - } - - @Override - protected void loadIcons(Cursor c, boolean animate) { - super.loadIcons(c, animate); - setCardTitle(mIconCard, mCurrentTheme.get(ThemesColumns.MODIFIES_ICONS), - getString(R.string.icon_label)); - } - - @Override - protected void loadNavBar(Cursor c, boolean animate) { - super.loadNavBar(c, animate); - setCardTitle(mNavBarCard, mCurrentTheme.get(ThemesColumns.MODIFIES_NAVIGATION_BAR), - getString(R.string.navbar_label)); - } - - @Override - protected void loadStyle(Cursor c, boolean animate) { - super.loadStyle(c, animate); - setCardTitle(mStyleCard, mCurrentTheme.get(ThemesColumns.MODIFIES_OVERLAYS), - getString(R.string.style_label)); - } - - @Override - protected void loadBootAnimation(Cursor c) { - super.loadBootAnimation(c); - setCardTitle(mBootAnimationCard, mCurrentTheme.get(ThemesColumns.MODIFIES_BOOT_ANIM), - getString(R.string.boot_animation_label)); - } - - @Override - public String getThemePackageName() { - if (mBaseThemePkgName == null) { - // check if the package name is defined in the arguments bundle - Bundle bundle = getArguments(); - if (bundle != null) { - mBaseThemePkgName = bundle.getString(ARG_BASE_THEME_PACKAGE_NAME); - } - } - return mBaseThemePkgName; - } - - private SurfaceView createSurfaceView() { - final Context context = getActivity(); - if (context == null) return null; - - SurfaceView sv = new SurfaceView(context); - final Resources res = context.getResources(); - FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( - res.getDimensionPixelSize(R.dimen.wallpaper_preview_width), - res.getDimensionPixelSize(R.dimen.theme_preview_height), - Gravity.CENTER_HORIZONTAL); - sv.setLayoutParams(params); - - return sv; - } - - private void addSurfaceView(SurfaceView sv) { - if (mShadowFrame.indexOfChild(mSurfaceView) < 0) { - int idx = mShadowFrame.indexOfChild(mWallpaper); - mShadowFrame.addView(sv, idx + 1); - } - } - - private void removeSurfaceView(SurfaceView sv) { - if (mShadowFrame.indexOfChild(mSurfaceView) >= 0) { - mShadowFrame.removeView(sv); - } - } - - /** - * Populates mBaseThemeSupportedComponents. - * @param pkgName Package name of the base theme used - */ - private void populateBaseThemeSupportedComponents(String pkgName) { - String selection = ThemesColumns.PKG_NAME + "=?"; - String[] selectionArgs = { pkgName }; - Cursor c = getActivity().getContentResolver().query(ThemesColumns.CONTENT_URI, - null, selection, selectionArgs, null); - if (c != null) { - if (c.moveToFirst()) { - List components = ThemeUtils.getAllComponents(); - final String baseThemePackageName = getThemePackageName(); - for (String component : components) { - int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int modifiesCompIdx = c.getColumnIndex(component); - - String pkg = pkgIdx >= 0 ? c.getString(pkgIdx) : null; - boolean supported = (modifiesCompIdx >= 0) && (c.getInt(modifiesCompIdx) == 1); - if (supported && baseThemePackageName.equals(pkg)) { - mBaseThemeSupportedComponents.add(component); - } - } - } - c.close(); - } - } -} diff --git a/src/com/cyngn/theme/chooser/NewFragmentStatePagerAdapter.java b/src/com/cyngn/theme/chooser/NewFragmentStatePagerAdapter.java deleted file mode 100644 index b60a631..0000000 --- a/src/com/cyngn/theme/chooser/NewFragmentStatePagerAdapter.java +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Copyright (C) 2011 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.cyngn.theme.chooser; - -import android.os.Bundle; -import android.os.Parcelable; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v4.app.FragmentTransaction; -import android.support.v4.view.PagerAdapter; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; - -import java.util.ArrayList; -import java.util.Arrays; - -/** - * Implementation of {@link android.support.v4.view.PagerAdapter} that - * uses a {@link android.support.v4.app.Fragment} to manage each page. This class also handles - * saving and restoring of fragment's state. - * - *

This version of the pager is more useful when there are a large number - * of pages, working more like a list view. When pages are not visible to - * the user, their entire fragment may be destroyed, only keeping the saved - * state of that fragment. This allows the pager to hold on to much less - * memory associated with each visited page as compared to - * {@link android.support.v4.app.FragmentPagerAdapter} at the cost of potentially more overhead when - * switching between pages. - * - *

When using FragmentPagerAdapter the host ViewPager must have a - * valid ID set.

- * - *

Subclasses only need to implement {@link #getItem(int)} - * and {@link #getCount()} to have a working adapter. - * - *

Here is an example implementation of a pager containing fragments of - * lists: - * - * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java - * complete} - * - *

The R.layout.fragment_pager resource of the top-level fragment is: - * - * {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml - * complete} - * - *

The R.layout.fragment_pager_list resource containing each - * individual fragment's layout is: - * - * {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml - * complete} - */ -public abstract class NewFragmentStatePagerAdapter extends PagerAdapter { - private static final String TAG = NewFragmentStatePagerAdapter.class.getSimpleName(); - private static final boolean DEBUG = false; - - private final FragmentManager mFragmentManager; - private FragmentTransaction mCurTransaction = null; - - private long[] mItemIds = new long[] {}; - private ArrayList mSavedState = new ArrayList(); - private ArrayList mFragments = new ArrayList(); - private Fragment mCurrentPrimaryItem = null; - - public NewFragmentStatePagerAdapter(FragmentManager fm) { - - mFragmentManager = fm; - createIdCache(); - } - - /** - * Return the Fragment associated with a specified position. - */ - public abstract Fragment getItem(int position); - - /** - * Return a unique identifier for the item at the given position. - */ - public abstract long getItemId(int position); - - @Override - public void startUpdate(ViewGroup container) { - } - - private void checkForIdChanges() { - long[] newItemIds = new long[getCount()]; - for (int i = 0; i < newItemIds.length; i++) { - newItemIds[i] = getItemId(i); - } - - if (!Arrays.equals(mItemIds, newItemIds)) { - ArrayList newSavedState = new ArrayList(); - ArrayList newFragments = new ArrayList(); - - for (int oldPosition = 0; oldPosition < mItemIds.length; oldPosition++) { - int newPosition = POSITION_NONE; - for (int i = 0; i < newItemIds.length; i++) { - if (mItemIds[oldPosition] == newItemIds[i]) { - newPosition = i; - break; - } - } - if (newPosition >= 0) { - if (oldPosition < mSavedState.size()) { - Fragment.SavedState savedState = mSavedState.get(oldPosition); - if (savedState != null) { - while (newSavedState.size() <= newPosition) { - newSavedState.add(null); - } - newSavedState.set(newPosition, savedState); - } - } - if (oldPosition < mFragments.size()) { - Fragment fragment = mFragments.get(oldPosition); - if (fragment != null) { - while (newFragments.size() <= newPosition) { - newFragments.add(null); - } - newFragments.set(newPosition, fragment); - } - } - } - } - - mItemIds = newItemIds; - mSavedState = newSavedState; - mFragments = newFragments; - } - } - - @Override - public void notifyDataSetChanged() { - checkForIdChanges(); - - super.notifyDataSetChanged(); - } - - /** - * Create the initial set of item IDs. Run this after you have set your adapter data. - */ - public void createIdCache() { - // If we have already stored ids, don't overwrite them - if (mItemIds.length == 0) { - // getCount might have overhead, so run it as late as possible - final int count = getCount(); - if (count > 0) { - mItemIds = new long[count]; - for (int i = 0; i < count; i++) { - mItemIds[i] = getItemId(i); - } - } - } - } - - @Override - public Object instantiateItem(ViewGroup container, int position) { - - createIdCache(); - - // If we already have this item instantiated, there is nothing - // to do. This can happen when we are restoring the entire pager - // from its saved state, where the fragment manager has already - // taken care of restoring the fragments we previously had instantiated. - if (mFragments.size() > position) { - Fragment f = mFragments.get(position); - if (f != null) { - return f; - } - } - - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - - Fragment fragment = getItem(position); - if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); - if (mSavedState.size() > position) { - Fragment.SavedState fss = mSavedState.get(position); - if (fss != null) { - fragment.setInitialSavedState(fss); - } - } - while (mFragments.size() <= position) { - mFragments.add(null); - } - fragment.setMenuVisibility(false); - fragment.setUserVisibleHint(false); - mFragments.set(position, fragment); - mCurTransaction.add(container.getId(), fragment); - - return fragment; - } - - @Override - public void destroyItem(ViewGroup container, int position, Object object) { - Fragment fragment = (Fragment)object; - - if (mCurTransaction == null) { - mCurTransaction = mFragmentManager.beginTransaction(); - } - if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object - + " v=" + ((Fragment)object).getView()); - while (mSavedState.size() <= position) { - mSavedState.add(null); - } - mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); - - // Only set the position to null if the fragment being removed is at "position" - // We do this because checkForIdChanges updates the mFragments list and if a fragment - // was removed then the fragment at "position" is not the fragment that was removed. - if (position < mFragments.size() && fragment == mFragments.get(position)) { - mFragments.set(position, null); - } - mCurTransaction.remove(fragment); - } - - @Override - public void setPrimaryItem(ViewGroup container, int position, Object object) { - Fragment fragment = (Fragment)object; - if (fragment != mCurrentPrimaryItem) { - if (mCurrentPrimaryItem != null) { - mCurrentPrimaryItem.setMenuVisibility(false); - mCurrentPrimaryItem.setUserVisibleHint(false); - } - if (fragment != null) { - fragment.setMenuVisibility(true); - fragment.setUserVisibleHint(true); - } - mCurrentPrimaryItem = fragment; - } - } - - @Override - public void finishUpdate(ViewGroup container) { - if (mCurTransaction != null) { - mCurTransaction.commitAllowingStateLoss(); - mCurTransaction = null; - mFragmentManager.executePendingTransactions(); - } - } - - @Override - public boolean isViewFromObject(View view, Object object) { - return ((Fragment)object).getView() == view; - } - - @Override - public Parcelable saveState() { - Bundle state = null; - - mItemIds = new long[getCount()]; - for (int i = 0; i < mItemIds.length; i++) { - mItemIds[i] = getItemId(i); - } - if (mSavedState.size() > 0) { - state = new Bundle(); - - if (mItemIds.length > 0) { - state.putLongArray("itemids", mItemIds); - } - - Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; - mSavedState.toArray(fss); - state.putParcelableArray("states", fss); - } - for (int i=0; i keys = bundle.keySet(); - for (String key: keys) { - if (key.startsWith("f")) { - int index = Integer.parseInt(key.substring(1)); - Fragment f = mFragmentManager.getFragment(bundle, key); - if (f != null) { - while (mFragments.size() <= index) { - mFragments.add(null); - } - f.setMenuVisibility(false); - mFragments.set(index, f); - } else { - Log.w(TAG, "Bad fragment at key " + key); - } - } - } - checkForIdChanges(); - } - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/chooser/NotificationHijackingService.java b/src/com/cyngn/theme/chooser/NotificationHijackingService.java deleted file mode 100644 index ca53791..0000000 --- a/src/com/cyngn/theme/chooser/NotificationHijackingService.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.chooser; - -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.provider.Settings; -import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; -import android.text.TextUtils; - -public class NotificationHijackingService extends NotificationListenerService { - private static final String TAG = NotificationHijackingService.class.getName(); - private static final String GOOGLE_PLAY_PACKAGE_NAME = "com.android.vending"; - private static final String ACTION_INSTALLED = - "com.android.vending.SUCCESSFULLY_INSTALLED_CLICKED"; - private static final String EXTRA_PACKAGE_NAME = "package_name"; - - @Override - public void onNotificationPosted(StatusBarNotification sbn) { - if (GOOGLE_PLAY_PACKAGE_NAME.equals(sbn.getPackageName())) { - PendingIntent contentIntent = sbn.getNotification().contentIntent; - if (contentIntent == null) return; - Intent intent = contentIntent.getIntent(); - if (intent == null) return; - String action = intent.getAction(); - if (ACTION_INSTALLED.equals(action)) { - String pkgName = intent.getStringExtra(EXTRA_PACKAGE_NAME); - try { - PackageInfo pi = getPackageManager().getPackageInfo(pkgName, 0); - if (pi != null) { - if (pi.themeInfo != null) { - cancelNotification(GOOGLE_PLAY_PACKAGE_NAME, sbn.getTag(), sbn.getId()); - } - } - } catch (PackageManager.NameNotFoundException e) { - } - } - } - } - - @Override - public void onNotificationRemoved(StatusBarNotification sbn) { - } - - // ensure that this notification listener is enabled. - // the service watches for google play notifications - public static void ensureEnabled(Context context) { - ComponentName me = new ComponentName(context, NotificationHijackingService.class); - String meFlattened = me.flattenToString(); - - String existingListeners = Settings.Secure.getString(context.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); - - if (!TextUtils.isEmpty(existingListeners)) { - if (existingListeners.contains(meFlattened)) { - return; - } else { - existingListeners += ":" + meFlattened; - } - } else { - existingListeners = meFlattened; - } - - Settings.Secure.putString(context.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - existingListeners); - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/chooser/PagerContainer.java b/src/com/cyngn/theme/chooser/PagerContainer.java deleted file mode 100644 index 1a5e51a..0000000 --- a/src/com/cyngn/theme/chooser/PagerContainer.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (c) 2012 Wireless Designs, LLC - * Portions copyright (C) 2014, The Cyanogen, Inc - * - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -package com.cyngn.theme.chooser; - -import android.content.Context; -import android.graphics.Point; -import android.support.v4.view.ThemeViewPager; -import android.support.v4.view.ViewPager; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewTreeObserver; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.DecelerateInterpolator; -import android.widget.FrameLayout; -import android.widget.LinearLayout; - -/** - * PagerContainer: A layout that displays a ViewPager with its children that are outside - * the typical pager bounds. - */ -public class PagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener { - private static final int ANIMATE_OUT_DURATION = 300; - private static final int ANIMATE_OUT_INTERPOLATE_FACTOR = 1; - private static final int ANIMATE_IN_DURATION = 300; - private static final int ANIMATE_IN_INTERPOLATE_FACTOR = 2; - - private ThemeViewPager mPager; - private Point mCenter = new Point(); - private Point mInitialTouch = new Point(); - private int mCollapsedHeight; - private boolean mIsAnimating = false; - - boolean mNeedsRedraw = false; - - public PagerContainer(Context context) { - this(context, null, 0); - } - - public PagerContainer(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PagerContainer(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - mCollapsedHeight = generateLayoutParams(attrs).height; - - //Disable clipping of children so non-selected pages are visible - setClipChildren(false); - } - - @Override - protected void onFinishInflate() { - try { - mPager = (ThemeViewPager) getChildAt(0); - mPager.setOnPageChangeListener(this); - } catch (Exception e) { - throw new IllegalStateException("The root child of PagerContainer must be a ViewPager"); - } - } - - public ThemeViewPager getViewPager() { - return mPager; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mCenter.x = w / 2; - mCenter.y = h / 2; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (mIsAnimating) return true; - return super.onInterceptTouchEvent(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - // Do not allow touch events to propagate if we are animating - if (mIsAnimating) return true; - - //We capture any touches not already handled by the ViewPager - // to implement scrolling from a touch outside the pager bounds. - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mInitialTouch.x = (int)ev.getX(); - mInitialTouch.y = (int)ev.getY(); - default: - ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y); - break; - } - - return mPager.dispatchTouchEvent(ev); - } - - @Override - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - //Force the container to redraw on scrolling. - //Without this the outer pages render initially and then stay static - if (mNeedsRedraw) invalidate(); - } - - @Override - public void onPageSelected(int position) { } - - @Override - public void onPageScrollStateChanged(int state) { - mNeedsRedraw = (state != ThemeViewPager.SCROLL_STATE_IDLE); - } - - public void setIsAnimating(boolean isAnimating) { - mIsAnimating = isAnimating; - } - - public void expand() { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); - params.height = LinearLayout.LayoutParams.MATCH_PARENT; - setLayoutParams(params); - - mPager.setExpanded(true); - - final int current = mPager.getCurrentItem(); - final int prevY = (int) getY(); - - //Since our viewpager's width is changing to fill the screen - //we must start the left/right children of the current page inwards on first draw - final int lChildPrevXf; - final int rChildPrevXf; - - if (current != 0) { - final View lchild = mPager.getViewForPosition(current - 1); - lChildPrevXf = (int) lchild.getX(); - } else { - lChildPrevXf = 0; - } - - if (current < mPager.getAdapter().getCount() - 1) { - View rchild = mPager.getViewForPosition(current + 1); - rChildPrevXf = (int) rchild.getX(); - } else { - rChildPrevXf = 0; - } - - - final ViewTreeObserver observer = mPager.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - if (current != 0) { - View lchild = mPager.getViewForPosition(current - 1); - lchild.setTranslationY(prevY - getY()); - lchild.setX(lChildPrevXf); - animateChildOut(lchild, -getWidth()); - } - - if (current < mPager.getAdapter().getCount() - 1) { - View rchild = mPager.getViewForPosition(current + 1); - rchild.setX(rChildPrevXf); - rchild.setTranslationY(prevY - getY()); - animateChildOut(rchild, getWidth()); - } - return false; - } - }); - } - - public void collapse() { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); - params.height = mCollapsedHeight; - setLayoutParams(params); - - mPager.setExpanded(false); - int current = mPager.getCurrentItem(); - final int prevY = (int) getY(); - - if (current != 0) { - View lchild = mPager.getViewForPosition(current - 1); - lchild.setTranslationY(0); - animateChildIn(lchild); - } - - if (current < mPager.getAdapter().getCount() - 1) { - View rchild = mPager.getViewForPosition(current + 1); - rchild.setTranslationY(0); - animateChildIn(rchild); - } - } - - private void animateChildOut(final View v, float endX) { - v.animate() - .translationX(endX) - .setDuration(ANIMATE_OUT_DURATION) - .setInterpolator(new AccelerateInterpolator(ANIMATE_OUT_INTERPOLATE_FACTOR)); - } - - private void animateChildIn(final View v) { - v.animate() - .translationX(0) - .setDuration(ANIMATE_IN_DURATION) - .setInterpolator(new DecelerateInterpolator(ANIMATE_IN_INTERPOLATE_FACTOR)); - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/chooser/ThemeFragment.java b/src/com/cyngn/theme/chooser/ThemeFragment.java deleted file mode 100644 index 12082e5..0000000 --- a/src/com/cyngn/theme/chooser/ThemeFragment.java +++ /dev/null @@ -1,3045 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.IntEvaluator; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.WallpaperManager; -import android.content.ActivityNotFoundException; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.Typeface; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.NinePatchDrawable; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Bundle; -import android.os.FileUtils; -import android.os.Handler; -import android.support.v4.app.Fragment; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.Loader; -import android.text.TextUtils; -import android.util.Log; -import android.util.MutableLong; -import android.util.SparseArray; -import android.view.Display; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewPropertyAnimator; -import android.view.ViewTreeObserver; -import android.view.animation.AccelerateInterpolator; -import android.view.animation.Animation; -import android.view.animation.DecelerateInterpolator; -import android.view.animation.ScaleAnimation; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.PopupMenu; -import android.widget.ProgressBar; -import android.widget.Space; -import android.widget.TextView; -import android.widget.Toast; - -import com.cyngn.theme.chooser.ComponentSelector.OnItemClickedListener; -import com.cyngn.theme.util.AudioUtils; -import com.cyngn.theme.util.BootAnimationHelper; -import com.cyngn.theme.util.CursorLoaderHelper; -import com.cyngn.theme.util.IconPreviewHelper; -import com.cyngn.theme.util.PreferenceUtils; -import com.cyngn.theme.util.ThemedTypefaceHelper; -import com.cyngn.theme.util.TypefaceHelperCache; -import com.cyngn.theme.util.Utils; -import com.cyngn.theme.util.WallpaperUtils; -import com.cyngn.theme.widget.BootAniImageView; -import com.cyngn.theme.widget.ConfirmCancelOverlay; -import com.cyngn.theme.widget.LockableScrollView; -import com.cyngn.theme.widget.ThemeTagLayout; - -import cyanogenmod.app.ThemeVersion; -import cyanogenmod.providers.CMSettings; -import cyanogenmod.providers.ThemesContract.PreviewColumns; -import cyanogenmod.providers.ThemesContract.ThemesColumns; -import cyanogenmod.themes.ThemeChangeRequest; -import cyanogenmod.themes.ThemeChangeRequest.RequestType; -import cyanogenmod.themes.ThemeManager; - -import org.cyanogenmod.internal.util.CmLockPatternUtils; -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidParameterException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.zip.ZipFile; - -import static android.Manifest.permission.READ_EXTERNAL_STORAGE; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; - -import static com.cyngn.theme.chooser.ComponentSelector.DEFAULT_COMPONENT_ID; - -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_INVALID; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ALL; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_STATUS_BAR; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_FONT; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ICONS; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_WALLPAPER; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_NAVIGATION_BAR; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_LOCKSCREEN; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_STYLE; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_BOOT_ANIMATION; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_RINGTONE; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_NOTIFICATION; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_ALARM; -import static com.cyngn.theme.util.CursorLoaderHelper.LOADER_ID_LIVE_LOCK_SCREEN; - -import static cyanogenmod.providers.CMSettings.Secure.LIVE_LOCK_SCREEN_ENABLED; - -import static org.cyanogenmod.internal.util.ThemeUtils.SYSTEM_TARGET_API; - -public class ThemeFragment extends Fragment implements LoaderManager.LoaderCallbacks, - ThemeManager.ThemeChangeListener, ThemeManager.ThemeProcessingListener { - private static final String TAG = ThemeFragment.class.getSimpleName(); - - public static final int ANIMATE_START_DELAY = 200; - public static final int ANIMATE_DURATION = 300; - public static final int ANIMATE_INTERPOLATE_FACTOR = 3; - public static final int ANIMATE_COMPONENT_CHANGE_DURATION = 200; - public static final int ANIMATE_COMPONENT_ICON_DELAY = 50; - public static final int ANIMATE_PROGRESS_IN_DURATION = 500; - public static final int ANIMATE_TITLE_OUT_DURATION = 400; - public static final int ANIMATE_PROGRESS_OUT_DURATION = 400; - public static final int ANIMATE_TITLE_IN_DURATION = 500; - public static final int ANIMATE_APPLY_LAYOUT_DURATION = 300; - - public static final String CURRENTLY_APPLIED_THEME = "currently_applied_theme"; - - private static final ComponentName COMPONENT_DIALER = - new ComponentName("com.android.dialer", "com.android.dialer.DialtactsActivity"); - private static final ComponentName COMPONENT_DIALERNEXT = - new ComponentName("com.cyngn.dialer", "com.android.dialer.DialtactsActivity"); - private static final ComponentName COMPONENT_MESSAGING = - new ComponentName("com.android.mms", "com.android.mms.ui.ConversationList"); - private static final ComponentName COMPONENT_CAMERANEXT = - new ComponentName("com.cyngn.cameranext", "com.android.camera.CameraLauncher"); - private static final ComponentName COMPONENT_CAMERA = - new ComponentName("com.android.camera2", "com.android.camera.CameraActivity"); - private static final ComponentName COMPONENT_BROWSER = - new ComponentName("com.android.browser", "com.android.browser.BrowserActivity"); - private static final ComponentName COMPONENT_SETTINGS = - new ComponentName("com.android.settings", "com.android.settings.Settings"); - private static final ComponentName COMPONENT_CALENDAR = - new ComponentName("com.android.calendar", "com.android.calendar.AllInOneActivity"); - private static final ComponentName COMPONENT_GALERY = - new ComponentName("com.android.gallery3d", "com.android.gallery3d.app.GalleryActivity"); - - private static final String CAMERA_NEXT_PACKAGE = "com.cyngn.cameranext"; - private static final String DIALER_NEXT_PACKAGE = "com.cyngn.dialer"; - - private static final int ADDITIONAL_CONTENT_SPACE_ID = 123456; - private static final long SLIDE_CONTENT_ANIM_DURATION = 300L; - private static final long LOCK_SCREEN_CARD_SCROLL_ANIMATION_DURATION = 400; - private static final long SHOW_LOCK_SCREEN_CARD_DELAY = 500; - - private static final int DEFAULT_WIFI_MARGIN = 0; - private static final int DEFAULT_CLOCK_COLOR = Color.WHITE; - - protected static final String WALLPAPER_NONE = ""; - protected static final String LOCKSCREEN_NONE = ""; - - protected static final String ARG_PACKAGE_NAME = "pkgName"; - protected static final String ARG_COMPONENT_ID = "cmpntId"; - protected static final String ARG_SKIP_LOADING_ANIM = "skipLoadingAnim"; - protected static final String ARG_ANIMATE_TO_LOCK_SCREEN_CARD = "animateToLockScreenCard"; - - private static final String LLS_PACKAGE_NAME = "com.cyngn.lockscreen.live"; - private static final String LLS_PROVIDER_NAME = - "com.cyngn.lockscreen.live.LockScreenProviderService"; - - private static final int PERMISSION_REQUEST = 100; - - protected static ComponentName[] sIconComponents; - - protected static TypefaceHelperCache sTypefaceHelperCache; - - /** - * Maps the card's resource ID to a theme component - */ - private final SparseArray mCardIdsToComponentTypes = new SparseArray(); - - protected String mPkgName; - protected Typeface mTypefaceNormal; - protected int mBatteryStyle; - - protected LockableScrollView mScrollView; - protected ViewGroup mScrollContent; - protected ViewGroup mPreviewContent; // Contains icons, font, nav/status etc. Not wallpaper - protected View mLoadingView; - - //Status Bar Views - protected ComponentCardView mStatusBarCard; - protected ImageView mBluetooth; - protected ImageView mWifi; - protected ImageView mSignal; - protected ImageView mBattery; - protected TextView mClock; - - // Other Misc Preview Views - protected FrameLayout mShadowFrame; - protected ImageView mWallpaper; - protected ViewGroup mStatusBar; - protected TextView mFontPreview; - protected ComponentCardView mStyleCard; - protected ComponentCardView mFontCard; - protected ComponentCardView mIconCard; - protected ComponentCardView mBootAnimationCard; - protected BootAniImageView mBootAnimation; - - // Nav Bar Views - protected ComponentCardView mNavBarCard; - protected ViewGroup mNavBar; - protected ImageView mBackButton; - protected ImageView mHomeButton; - protected ImageView mRecentButton; - - // Title Card Views - protected ViewGroup mTitleCard; - protected ViewGroup mTitleLayout; - protected TextView mTitle; - protected TextView mAuthor; - protected ImageView mCustomize; - protected ImageView mOverflow; - protected ImageView mDelete; - protected ImageView mReset; - protected ProgressBar mProgress; - - // Additional Card Views - protected LinearLayout mAdditionalCards; - protected WallpaperCardView mWallpaperCard; - protected WallpaperCardView mLockScreenCard; - - // Style views - protected ImageView mStylePreview; - - // Sound cards - protected ComponentCardView mRingtoneCard; - protected ImageView mRingtonePlayPause; - protected ComponentCardView mNotificationCard; - protected ImageView mNotificationPlayPause; - protected ComponentCardView mAlarmCard; - protected ImageView mAlarmPlayPause; - protected Map mMediaPlayers; - - protected Handler mHandler; - - protected int mActiveCardId = -1; - protected ComponentSelector mSelector; - // Supported components for the theme this fragment represents - protected Map mSelectedComponentsMap = new HashMap(); - protected Long mSelectedWallpaperComponentId; - // Current system theme configuration as component -> pkgName - protected Map mCurrentTheme = new HashMap(); - protected MutableLong mCurrentWallpaperComponentId = new MutableLong(DEFAULT_COMPONENT_ID); - // Set of components available in the base theme - protected HashSet mBaseThemeSupportedComponents = new HashSet(); - protected Cursor mCurrentCursor; - protected int mCurrentLoaderId; - protected boolean mThemeResetting; - protected boolean mSkipLoadingAnim; - - // Accept/Cancel overlay - protected ConfirmCancelOverlay mConfirmCancelOverlay; - - // Customize/Reset theme layout - protected View mCustomizeResetLayout; - protected View mResetButton; - protected View mCustomizeButton; - protected View mDismissButton; - - // Processing theme layout - protected View mProcessingThemeLayout; - - protected ThemeTagLayout mThemeTagLayout; - - protected View mClickableView; - protected String mBaseThemePkgName; - - protected Uri mExternalWallpaperUri; - protected Uri mExternalLockscreenUri; - - protected boolean mExpanded; - protected boolean mProcessingResources; - protected boolean mApplyThemeOnPopulated; - - protected boolean mIsLegacyTheme; - - private Runnable mAfterPermissionGrantedRunnable; - - private static final int mThemeVersion = ThemeVersion.getVersion(); - - protected boolean mShowLockScreenSelectorAfterContentLoaded; - - protected enum CustomizeResetAction { - Customize, - Reset, - Dismiss - } - - static ThemeFragment newInstance(String pkgName, boolean skipLoadingAnim) { - ThemeFragment f = new ThemeFragment(); - Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, pkgName); - args.putBoolean(ARG_SKIP_LOADING_ANIM, skipLoadingAnim); - args.putLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); - f.setArguments(args); - return f; - } - - /** - * When creating, retrieve this instance's number from its arguments. - */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Context context = getActivity(); - mPkgName = getArguments().getString(ARG_PACKAGE_NAME); - mSkipLoadingAnim = getArguments().getBoolean(ARG_SKIP_LOADING_ANIM); - // TODO: Load from settings once available - mBatteryStyle = 0;/*Settings.System.getInt(context.getContentResolver(), - Settings.System.STATUS_BAR_BATTERY_STYLE, 0);*/ - - getIconComponents(context); - if (sTypefaceHelperCache == null) { - sTypefaceHelperCache = TypefaceHelperCache.getInstance(); - } - ThemedTypefaceHelper helper = sTypefaceHelperCache.getHelperForTheme(context, mPkgName); - mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); - - mHandler = new Handler(); - - mCardIdsToComponentTypes.put(R.id.status_bar_container, MODIFIES_STATUS_BAR); - mCardIdsToComponentTypes.put(R.id.font_preview_container, MODIFIES_FONTS); - mCardIdsToComponentTypes.put(R.id.icon_container, MODIFIES_ICONS); - mCardIdsToComponentTypes.put(R.id.navigation_bar_container, MODIFIES_NAVIGATION_BAR); - mCardIdsToComponentTypes.put(R.id.wallpaper_card, MODIFIES_LAUNCHER); - mCardIdsToComponentTypes.put(R.id.lockscreen_card, MODIFIES_LOCKSCREEN); - mCardIdsToComponentTypes.put(R.id.style_card, MODIFIES_OVERLAYS); - mCardIdsToComponentTypes.put(R.id.bootani_preview_container, MODIFIES_BOOT_ANIM); - mCardIdsToComponentTypes.put(R.id.ringtone_preview_container, MODIFIES_RINGTONES); - mCardIdsToComponentTypes.put(R.id.notification_preview_container, MODIFIES_NOTIFICATIONS); - mCardIdsToComponentTypes.put(R.id.alarm_preview_container, MODIFIES_ALARMS); - - mMediaPlayers = new HashMap(3); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.fragment_pager_list, container, false); - - mScrollView = (LockableScrollView) v.findViewById(android.R.id.list); - mScrollView.setScrollingEnabled(false); - mScrollContent = (ViewGroup) mScrollView.getChildAt(0); - mPreviewContent = (ViewGroup) v.findViewById(R.id.preview_container); - mLoadingView = v.findViewById(R.id.loading_view); - mThemeTagLayout = (ThemeTagLayout) v.findViewById(R.id.tag_layout); - - // Status Bar - mStatusBarCard = (ComponentCardView) v.findViewById(R.id.status_bar_container); - mStatusBar = (ViewGroup) v.findViewById(R.id.status_bar); - mBluetooth = (ImageView) v.findViewById(R.id.bluetooth_icon); - mWifi = (ImageView) v.findViewById(R.id.wifi_icon); - mSignal = (ImageView) v.findViewById(R.id.signal_icon); - mBattery = (ImageView) v.findViewById(R.id.battery); - mClock = (TextView) v.findViewById(R.id.clock); - - // Wallpaper / Font / Icons / etc - mWallpaper = (ImageView) v.findViewById(R.id.wallpaper); - mFontCard = (ComponentCardView) v.findViewById(R.id.font_preview_container); - mFontPreview = (TextView) v.findViewById(R.id.font_preview); - mFontPreview.setTypeface(mTypefaceNormal); - mIconCard = (ComponentCardView) v.findViewById(R.id.icon_container); - mShadowFrame = (FrameLayout) v.findViewById(R.id.shadow_frame); - mStyleCard = (ComponentCardView) v.findViewById(R.id.style_card); - mStylePreview = (ImageView) v.findViewById(R.id.style_preview); - mBootAnimationCard = (ComponentCardView) v.findViewById(R.id.bootani_preview_container); - mBootAnimation = - (BootAniImageView) mBootAnimationCard.findViewById(R.id.bootani_preview); - mRingtoneCard = (ComponentCardView) v.findViewById(R.id.ringtone_preview_container); - mRingtonePlayPause = (ImageView) mRingtoneCard.findViewById(R.id.play_pause); - mNotificationCard = (ComponentCardView) v.findViewById(R.id.notification_preview_container); - mNotificationPlayPause = (ImageView) mNotificationCard.findViewById(R.id.play_pause); - mAlarmCard = (ComponentCardView) v.findViewById(R.id.alarm_preview_container); - mAlarmPlayPause = (ImageView) mAlarmCard.findViewById(R.id.play_pause); - - // Nav Bar - mNavBarCard = (ComponentCardView) v.findViewById(R.id.navigation_bar_container); - mNavBar = (ViewGroup) v.findViewById(R.id.navigation_bar); - mBackButton = (ImageView) v.findViewById(R.id.back_button); - mHomeButton = (ImageView) v.findViewById(R.id.home_button); - mRecentButton = (ImageView) v.findViewById(R.id.recent_button); - - // Title Card - mTitleCard = (ViewGroup)v.findViewById(R.id.title_card); - mTitleLayout = (ViewGroup) v.findViewById(R.id.title_layout); - mTitle = (TextView) v.findViewById(R.id.title); - mAuthor = (TextView) v.findViewById(R.id.author); - mProgress = (ProgressBar) v.findViewById(R.id.apply_progress); - mOverflow = (ImageView) v.findViewById(R.id.overflow); - mOverflow.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (isShowingConfirmCancelOverlay()) { - hideConfirmCancelOverlay(); - } else if (isShowingCustomizeResetLayout()) { - hideCustomizeResetLayout(); - } - - PopupMenu popupmenu = new PopupMenu(getActivity(), mTitleCard, Gravity.END); - popupmenu.getMenuInflater().inflate(R.menu.overflow, popupmenu.getMenu()); - - Menu menu = popupmenu.getMenu(); - if (CURRENTLY_APPLIED_THEME.equals(mPkgName) || - mPkgName.equals(Utils.getDefaultThemePackageName(getActivity())) || - mPkgName.equals(ThemeConfig.SYSTEM_DEFAULT)) { - menu.findItem(R.id.menu_delete).setEnabled(false); - } - if (!mThemeTagLayout.isCustomizedTagEnabled()) { - menu.findItem(R.id.menu_reset).setVisible(false); - } - - popupmenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - return onPopupMenuItemClick(item); - } - }); - popupmenu.show(); - } - }); - mCustomize = (ImageView) v.findViewById(R.id.customize); - mCustomize.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - if (!isShowingConfirmCancelOverlay() && !isShowingCustomizeResetLayout()) { - getChooserActivity().expand(); - } - } - }); - - mDelete = (ImageView) v.findViewById(R.id.delete); - mDelete.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showDeleteThemeOverlay(); - } - }); - if (Utils.getDefaultThemePackageName(getActivity()).equals(mPkgName) || - ThemeConfig.SYSTEM_DEFAULT.equals(mPkgName)) { - mDelete.setVisibility(View.GONE); - } - - mReset = (ImageView) v.findViewById(R.id.reset); - mReset.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - showResetThemeOverlay(); - } - }); - mReset.setVisibility(View.GONE); - - if (!Utils.hasNavigationBar(getActivity())) { - adjustScrollViewPaddingTop(); - mNavBarCard.setVisibility(View.GONE); - } - - // Additional cards which should hang out offscreen until expanded - mAdditionalCards = (LinearLayout) v.findViewById(R.id.additional_cards); - - mWallpaperCard = (WallpaperCardView) v.findViewById(R.id.wallpaper_card); - mLockScreenCard = (WallpaperCardView) v.findViewById(R.id.lockscreen_card); - int translationY = getDistanceToMoveBelowScreen(mAdditionalCards); - mAdditionalCards.setTranslationY(translationY); - - mConfirmCancelOverlay = (ConfirmCancelOverlay) v.findViewById(R.id.confirm_cancel_overlay); - mClickableView = v.findViewById(R.id.clickable_view); - - mCustomizeResetLayout = v.findViewById(R.id.customize_reset_theme_layout); - mDismissButton = mCustomizeResetLayout.findViewById(R.id.btn_dismiss); - mDismissButton.setOnClickListener(mCustomizeResetClickListener); - mResetButton = mCustomizeResetLayout.findViewById(R.id.btn_reset); - mResetButton.setOnClickListener(mCustomizeResetClickListener); - mCustomizeButton = mCustomizeResetLayout.findViewById(R.id.btn_customize); - mCustomizeButton.setOnClickListener(mCustomizeResetClickListener); - - mProcessingThemeLayout = v.findViewById(R.id.processing_theme_layout); - - if (mPkgName.equals(Utils.getDefaultThemePackageName(getActivity()))) { - mThemeTagLayout.setDefaultTagEnabled(true); - } - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { - mThemeTagLayout.setUpdatedTagEnabled(true); - } - - if (mSkipLoadingAnim) { - mLoadingView.setVisibility(View.GONE); - mTitleLayout.setAlpha(1f); - } - - getLoaderManager().initLoader(LOADER_ID_ALL, null, this); - - setupCardClickListeners(v); - - return v; - } - - @Override - public void onPause() { - super.onPause(); - stopMediaPlayers(); - } - - @Override - public void onResume() { - super.onResume(); - ThemeManager tm = getThemeManager(); - if (tm != null) { - if (isThemeProcessing()) { - tm.registerProcessingListener(this); - mProcessingThemeLayout.setVisibility(View.VISIBLE); - mCustomize.setVisibility(View.INVISIBLE); - mCustomize.setAlpha(0f); - if (mDelete.getVisibility() != View.GONE) { - mDelete.setVisibility(View.INVISIBLE); - mDelete.setAlpha(0f); - } - mProcessingResources = true; - } else { - mCustomize.setVisibility(View.VISIBLE); - mCustomize.setAlpha(1f); - if (mDelete.getVisibility() != View.GONE) { - mDelete.setVisibility(View.VISIBLE); - mDelete.setAlpha(1f); - } - mProcessingResources = false; - } - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - freeMediaPlayers(); - ThemeManager tm = getThemeManager(); - if (tm != null) { - tm.removeClient(this); - tm.unregisterProcessingListener(this); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, - int[] grantResults) { - if (requestCode == PERMISSION_REQUEST) { - int N = permissions.length; - for (int i = 0; i < N; i++) { - if (READ_EXTERNAL_STORAGE.equals(permissions[i])) { - if (grantResults[i] == PERMISSION_GRANTED) { - // Run the runnable now that we have been granted permission - if (mAfterPermissionGrantedRunnable != null) { - mAfterPermissionGrantedRunnable.run(); - mAfterPermissionGrantedRunnable = null; - } - } else { - // inform the user that they will be unable to pick an image because - // we were not granted permission to do so - Toast.makeText(getActivity(), - R.string.read_external_permission_denied_message, - Toast.LENGTH_LONG).show(); - } - } - } - } - } - - @Override - public void onProgress(int progress) { - mProgress.setProgress(progress); - } - - private void setLiveLockScreenAsKeyguard(boolean setLLS) { - ComponentName cn = null; - if (setLLS) { - try { - final String[] permissions = Utils.getDangerousPermissionsNotGranted(getActivity(), - LLS_PACKAGE_NAME); - if (permissions.length > 0) { - Intent reqIntent = Utils.buildPermissionGrantRequestIntent(getActivity(), - LLS_PACKAGE_NAME, permissions); - if (reqIntent != null) { - startActivity(reqIntent); - } - } - cn = new ComponentName(LLS_PACKAGE_NAME, LLS_PROVIDER_NAME); - } catch (InvalidParameterException e) { - Log.e(TAG, "Package Manager couldn't find package " + LLS_PACKAGE_NAME, e); - return; - } - } - - CmLockPatternUtils lockPatternUtils = new CmLockPatternUtils(getActivity()); - try { - lockPatternUtils.setThirdPartyKeyguard(cn); - } catch (PackageManager.NameNotFoundException e) { - // we should not be here! - } - } - - @Override - public void onFinish(boolean isSuccess) { - // We post a runnable to mHandler so the client is removed from the same thread - mHandler.post(new Runnable() { - @Override - public void run() { - ThemeManager tm = getThemeManager(); - if (tm != null) tm.removeClient(ThemeFragment.this); - } - }); - if (isSuccess) { - if (mExternalLockscreenUri != null) { - // Handle setting an external wallpaper in a separate thread - // Need to do this AFTER ThemeMgr is done processing our change request. - // The external lock screen that we just applied would be removed when - // the change request is setting/clearing the lock screen - new Thread(mApplyExternalLockscreenRunnable).start(); - } - Map appliedComponents = getComponentsToApply(); - boolean modLLS = appliedComponents.containsKey(MODIFIES_LIVE_LOCK_SCREEN); - if (modLLS) { - String pkgName = appliedComponents.get(MODIFIES_LIVE_LOCK_SCREEN); - if (pkgName.equals(LOCKSCREEN_NONE)) { - setLiveLockScreenAsKeyguard(false); - } else { - setLiveLockScreenAsKeyguard(true); - } - } - mProgress.setProgress(100); - animateProgressOut(); - } - getChooserActivity().themeChangeEnd(isSuccess); - } - - @Override - public void onFinishedProcessing(String pkgName) { - if (pkgName.equals(mPkgName) || pkgName.equals(mBaseThemePkgName)) { - ThemeManager tm = getThemeManager(); - if (tm != null) { - tm.unregisterProcessingListener(this); - } - } - } - - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - if (mThemeTagLayout == null) return; - - if (!isVisibleToUser) { - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { - mThemeTagLayout.setUpdatedTagEnabled(true); - } - } else { - if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { - PreferenceUtils.removeUpdatedTheme(getActivity(), mPkgName); - } - } - } - - public void setWallpaperImageUri(Uri uri) { - mExternalWallpaperUri = uri; - final Point size = new Point(mWallpaper.getWidth(), mWallpaper.getHeight()); - final Drawable wp = getWallpaperDrawableFromUri(uri, size); - mWallpaperCard.setWallpaper(wp); - mWallpaper.setImageDrawable(wp); - // remove the entry from mSelectedComponentsMap - mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LAUNCHER); - } - - public void setLockscreenImageUri(Uri uri) { - mExternalLockscreenUri = uri; - final Point size = new Point(mLockScreenCard.getWidth(), mLockScreenCard.getHeight()); - final Drawable wp = getWallpaperDrawableFromUri(uri, size); - if (mLockScreenCard.isShowingEmptyView()) { - mLockScreenCard.setEmptyViewEnabled(false); - } - mLockScreenCard.setWallpaper(wp); - // remove the entry from mSelectedComponentsMap - mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN); - mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LOCKSCREEN); - } - - protected Drawable getWallpaperDrawableFromUri(Uri uri, Point size) { - final Context context = getActivity(); - final Resources res = context.getResources(); - Bitmap bmp = WallpaperUtils.createPreview(size, context, uri, null, res, 0, 0, false); - if (bmp != null) { - return new BitmapDrawable(res, bmp); - } - return null; - } - - protected ChooserActivity getChooserActivity() { - return (ChooserActivity) getActivity(); - } - - private void adjustScrollViewPaddingTop() { - Resources res = getResources(); - int extraPadding = - res.getDimensionPixelSize(R.dimen.navigation_bar_height) / 2; - mScrollView.setPadding(mScrollView.getPaddingLeft(), - mScrollView.getPaddingTop() + extraPadding, mScrollView.getPaddingRight(), - mScrollView.getPaddingBottom()); - } - - protected boolean isThemeProcessing() { - ThemeManager tm = getThemeManager(); - if (tm != null) { - final String pkgName = mBaseThemePkgName != null ? mBaseThemePkgName : mPkgName; - return tm.isThemeBeingProcessed(pkgName); - } - return false; - } - - protected boolean onPopupMenuItemClick(MenuItem item) { - switch(item.getItemId()) { - /* TODO: Add back in once there is UX available for this feature - case R.id.menu_author: - Toast.makeText(getActivity(), - "Not supported", - Toast.LENGTH_LONG).show(); - break; - */ - case R.id.menu_delete: - showDeleteThemeOverlay(); - break; - } - - return true; - } - - public void expand() { - if (mCurrentLoaderId == LOADER_ID_ALL && mCurrentCursor != null) { - loadAdditionalCards(mCurrentCursor); - // we don't need this now that the additional cards are loaded, and - // we don't want to re-load these cards if the we expand again. - mCurrentCursor = null; - } - mClickableView.setVisibility(View.GONE); - mScrollView.setScrollingEnabled(true); - // Full width and height! - ViewGroup content = (ViewGroup) mScrollView.getParent(); - content.setPadding(0, 0, 0, 0); - ViewGroup.LayoutParams layoutParams = mPreviewContent.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; - mPreviewContent.setLayoutParams(layoutParams); - mScrollView.setPadding(0,0,0,0); - - // The parent of the wallpaper squishes the wp slightly because of padding from the 9 patch - // When the parent expands, the wallpaper returns to regular size which creates an - // undesireable effect. - Rect padding = new Rect(); - NinePatchDrawable bg = (NinePatchDrawable) mShadowFrame.getBackground(); - bg.getPadding(padding); - mIconCard.setPadding(padding.left, padding.top, padding.right, padding.bottom); - mShadowFrame.setBackground(null); - mShadowFrame.setPadding(0, 0, 0, 0); - - // Off screen cards will become visible and then be animated in - mWallpaperCard.setVisibility(View.VISIBLE); - - // Expand the children - int top = (int) getResources() - .getDimension(R.dimen.expanded_card_margin_top); - for (int i = 0; i < mPreviewContent.getChildCount(); i++) { - ComponentCardView child = (ComponentCardView) mPreviewContent.getChildAt(i); - - LinearLayout.LayoutParams lparams = - (LinearLayout.LayoutParams) child.getLayoutParams(); - if (child == mStatusBarCard) { - int statusBarHeight = getResources() - .getDimensionPixelSize(R.dimen.status_bar_height); - lparams.setMargins(0, top + statusBarHeight, 0, 0); - } else { - lparams.setMargins(0, top, 0, 0); - } - - child.setLayoutParams(lparams); - child.expand(false); - } - - // Expand the additional children. - mAdditionalCards.setVisibility(View.VISIBLE); - for (int i = 0; i < mAdditionalCards.getChildCount(); i++) { - View v = mAdditionalCards.getChildAt(i); - if (v instanceof ComponentCardView) { - ComponentCardView card = (ComponentCardView) v; - card.setVisibility(View.VISIBLE); - card.expand(true); - } - } - - // Collect the present position of all the children. The next layout/draw cycle will - // change these bounds since we just expanded them. Then we can animate from prev location - // to the new location. Note that the order of these calls matter as they all - // add themselves to the root layout as overlays - mScrollView.requestLayout(); - animateWallpaperOut(); - animateTitleCard(true, false); - animateChildren(true, getChildrensGlobalBounds(mPreviewContent)); - animateExtras(true); - mSelector = getChooserActivity().getComponentSelector(); - mSelector.setOnItemClickedListener(mOnComponentItemClicked); - if (mBootAnimation != null) mBootAnimation.start(); - hideThemeTagLayout(); - mExpanded = true; - } - - - - // Returns the boundaries for all the children of parent relative to the app window - private List getChildrensGlobalBounds(ViewGroup parent) { - List bounds = new ArrayList(); - for (int i = 0; i < parent.getChildCount(); i++) { - final View v = parent.getChildAt(i); - int[] pos = new int[2]; - v.getLocationInWindow(pos); - Rect boundary = new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1]+v.getHeight()); - bounds.add(boundary); - } - return bounds; - } - - public void performClick(boolean clickedOnContent) { - // Don't do anything if the theme is being processed - if (mProcessingThemeLayout.getVisibility() == View.VISIBLE) return; - - if (clickedOnContent) { - showApplyThemeOverlay(); - } else { - if (isShowingConfirmCancelOverlay()) { - hideConfirmCancelOverlay(); - } - } - } - - public void fadeOutCards(Runnable endAction) { - for (int i = 0; i < mPreviewContent.getChildCount(); i++) { - ComponentCardView v = (ComponentCardView) mPreviewContent.getChildAt(i); - v.animateFadeOut(); - } - mHandler.postDelayed(endAction, ComponentCardView.CARD_FADE_DURATION); - } - - public void collapse(final boolean applyTheme) { - mScrollView.setScrollingEnabled(false); - - // Pad the view so it appears thinner - ViewGroup content = (ViewGroup) mScrollView.getParent(); - Resources r = mScrollView.getContext().getResources(); - int leftRightPadding = (int) r.getDimension(R.dimen.collapsed_theme_page_padding); - content.setPadding(leftRightPadding, 0, leftRightPadding, 0); - - if (applyTheme) { - final boolean customized = isThemeCustomized(); - mThemeTagLayout.setCustomizedTagEnabled(customized); - mReset.setVisibility(customized ? View.VISIBLE : View.GONE); - } - - //Move the theme preview so that it is near the center of page per spec - int paddingTop = (int) r.getDimension(R.dimen.collapsed_theme_page_padding_top); - if (!Utils.hasNavigationBar(getActivity())) { - paddingTop += - r.getDimensionPixelSize(R.dimen.navigation_bar_height) / 2; - } - mScrollView.setPadding(0, paddingTop, 0, 0); - - // During expand the wallpaper size decreases slightly to makeup for 9patch padding - // so when we collapse we should increase it again. - mShadowFrame.setBackgroundResource(R.drawable.bg_themepreview_shadow); - Rect padding = new Rect(); - final NinePatchDrawable bg = (NinePatchDrawable) mShadowFrame.getBackground(); - bg.getPadding(padding); - mShadowFrame.setPadding(padding.left, padding.top, padding.right, padding.bottom); - - // Gradually fade the drop shadow back in or else it will be out of place - ValueAnimator shadowAnimation = ValueAnimator.ofObject(new IntEvaluator(), 0, 255); - shadowAnimation.setDuration(ANIMATE_DURATION); - shadowAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - bg.setAlpha((Integer) animator.getAnimatedValue()); - } - - }); - shadowAnimation.start(); - - //Move the title card back in - mTitleCard.setVisibility(View.VISIBLE); - mTitleCard.setTranslationY(0); - - // Shrink the height - ViewGroup.LayoutParams layoutParams = mPreviewContent.getLayoutParams(); - Resources resources = mPreviewContent.getResources(); - layoutParams.height = (int) resources.getDimension(R.dimen.theme_preview_height); - - mScrollView.requestLayout(); - List bounds = getChildrensGlobalBounds(mPreviewContent); - for (int i = 0; i < mPreviewContent.getChildCount(); i++) { - ComponentCardView child = (ComponentCardView) mPreviewContent.getChildAt(i); - LinearLayout.LayoutParams lparams = - (LinearLayout.LayoutParams) child.getLayoutParams(); - lparams.setMargins(0, 0, 0, 0); - - if (child.getId() == R.id.icon_container) { - int top = (int) child.getResources() - .getDimension(R.dimen.collapsed_icon_card_margin_top); - lparams.setMargins(0, top, 0, 0); - } else if (child.getId() == R.id.font_preview_container) { - int top = (int) child.getResources() - .getDimension(R.dimen.collapsed_font_card_margin_top); - lparams.setMargins(0, top, 0, 0); - } else if (child.getId() == R.id.navigation_bar_container) { - int top = (int) child.getResources() - .getDimension(R.dimen.collapsed_navbar_card_margin_top); - lparams.setMargins(0, top, 0, 0); - } - - child.getLayoutParams(); - child.collapse(); - } - - // Collapse additional cards - for (int i = 0; i < mAdditionalCards.getChildCount(); i++) { - View v = mAdditionalCards.getChildAt(i); - if (v instanceof ComponentCardView) { - ComponentCardView card = (ComponentCardView) v; - card.setVisibility(View.VISIBLE); - card.collapse(); - } - } - - animateChildren(false, bounds); - animateExtras(false); - animateWallpaperIn(); - animateTitleCard(false, applyTheme); - if (mBootAnimation != null) mBootAnimation.stop(); - stopMediaPlayers(); - showThemeTagLayout(); - - // Need to set the wallpaper background to black if the user has selected to apply - // the "none" wallpaper - if (applyTheme) { - String pkgName = mSelectedComponentsMap.get(ThemesColumns.MODIFIES_LAUNCHER); - if (pkgName != null && pkgName.length() == 0) { - mWallpaper.setImageResource(R.drawable.wallpaper_none_bg); - } - // we do this here instead of in applyTheme() because this can take a bit longer - // to propagate the change from WallpaperManager back to us - if (mExternalWallpaperUri != null) { - // Handle setting an external wallpaper in a separate thread - new Thread(mApplyExternalWallpaperRunnable).start(); - } - } - mExpanded = false; - } - - // This will animate the children's vertical positions between the previous bounds and the - // new bounds which occur on the next draw - private void animateChildren(final boolean isExpanding, final List prevBounds) { - final ViewGroup root = (ViewGroup) getActivity().getWindow() - .getDecorView().findViewById(android.R.id.content); - - final Resources res = getResources(); - final float yOffset = - res.getDimensionPixelSize(R.dimen.expand_collapse_child_offset) - * (isExpanding ? -1 : 1); - // Grab the child's new location and animate from prev to current loc. - final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - - for (int i = mPreviewContent.getChildCount() - 1; i >= 0; i--) { - final ComponentCardView v = (ComponentCardView) mPreviewContent.getChildAt(i); - - float prevY; - float endY; - float prevHeight; - float endHeight; - if (i >= prevBounds.size()) { - // View is being created - prevY = mPreviewContent.getTop() + mPreviewContent.getHeight(); - endY = v.getY(); - prevHeight = v.getHeight(); - endHeight = v.getHeight(); - } else { - Rect boundary = prevBounds.get(i); - prevY = boundary.top; - prevHeight = boundary.height(); - - int[] endPos = new int[2]; - v.getLocationInWindow(endPos); - endY = endPos[1]; - endHeight = v.getHeight(); - } - - int paddingTop = v.getPaddingTop() / 2; - float dy = (prevY - endY - paddingTop) + (prevHeight - endHeight) / 2; - dy += yOffset; - v.setTranslationY(dy); - root.getOverlay().add(v); - - // Expanding has a delay while the wallpaper begins to fade out - // Collapsing is opposite of this so wallpaper will have the delay instead - int startDelay = isExpanding ? ANIMATE_START_DELAY : 0; - - v.animate() - .setStartDelay(startDelay) - .translationY(0) - .setDuration(ANIMATE_DURATION) - .setInterpolator( - new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)) - .withEndAction(new Runnable() { - public void run() { - root.getOverlay().remove(v); - mPreviewContent.addView(v, 0); - } - }); - v.postDelayed(new Runnable() { - public void run() { - if (isExpanding) { - v.animateExpand(); - } - } - }, ANIMATE_DURATION / 2); - } - return true; - } - }); - } - - private void animateExtras(final boolean isExpanding) { - int[] pos = new int[2]; - mAdditionalCards.getLocationInWindow(pos); - final ViewGroup parent = (ViewGroup) mAdditionalCards.getParent(); - final ViewGroup root = (ViewGroup) getActivity().getWindow() - .getDecorView().findViewById(android.R.id.content); - - // During a collapse we don't want the card to shrink so add it to the overlay now - // During an expand we want the card to expand so add it to the overlay post-layout - if (!isExpanding) { - root.getOverlay().add(mAdditionalCards); - } - - // Expanding has a delay while the wallpaper begins to fade out - // Collapsing is opposite of this so wallpaper will have the delay instead - final int startDelay = isExpanding ? ANIMATE_START_DELAY : 0; - final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - - int translationY = 0; - if (isExpanding) { - root.getOverlay().add(mAdditionalCards); - } else { - translationY = getDistanceToMoveBelowScreen(mAdditionalCards); - } - - int duration = isExpanding ? ANIMATE_DURATION + 100 : ANIMATE_DURATION; - mAdditionalCards.animate() - .setStartDelay(startDelay) - .translationY(translationY) - .setDuration(duration) - .setInterpolator( - new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)) - .withEndAction(new Runnable() { - public void run() { - if (!isExpanding) { - mAdditionalCards.setVisibility(View.INVISIBLE); - } - root.getOverlay().remove(mAdditionalCards); - parent.addView(mAdditionalCards); - } - }); - return false; - } - }); - } - - private int getDistanceToMoveBelowScreen(View v) { - Display display = getActivity().getWindowManager().getDefaultDisplay(); - Point p = new Point(); - display.getSize(p); - int heightId = getResources() - .getIdentifier("navigation_bar_height", "dimen", "android"); - int navbar_height = getResources().getDimensionPixelSize(heightId); - int[] pos = new int[2]; - v.getLocationInWindow(pos); - return p.y + navbar_height - pos[1]; - } - - private void animateTitleCard(final boolean expand, final boolean applyTheme) { - final ViewGroup parent = (ViewGroup) mTitleCard.getParent(); - // Get current location of the title card - int[] location = new int[2]; - mTitleCard.getLocationOnScreen(location); - final int prevY = location[1]; - final int position = parent.indexOfChild(mTitleCard); - - final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - - final ViewGroup root = (ViewGroup) getActivity().getWindow() - .getDecorView().findViewById(android.R.id.content); - - root.getOverlay().add(mTitleCard); - - //Move title card back where it was before the relayout - float alpha = 1f; - if (expand) { - int[] endPos = new int[2]; - mTitleCard.getLocationInWindow(endPos); - int endY = endPos[1]; - mTitleCard.setTranslationY(prevY - endY); - alpha = 0; - } else { - } - - // Fade the title card and move it out of the way - mTitleCard.animate() - .alpha(alpha) - .setDuration(ANIMATE_DURATION) - .withEndAction(new Runnable() { - public void run() { - root.getOverlay().remove(mTitleCard); - parent.addView(mTitleCard, position); - if (expand) { - mTitleCard.setVisibility(View.INVISIBLE); - } else { - mTitleCard.setVisibility(View.VISIBLE); - mClickableView.setVisibility(View.VISIBLE); - if (applyTheme) { - // The title card is the last animation when collapsing so - // we will handle applying the theme, if applicable, here - applyTheme(); - } - } - } - }); - return true; - } - }); - } - - private void animateWallpaperOut() { - final ViewGroup root = (ViewGroup) getActivity().getWindow() - .getDecorView().findViewById(android.R.id.content); - - int[] location = new int[2]; - mWallpaper.getLocationOnScreen(location); - - final int prevY = location[1]; - - final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); - observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { - public boolean onPreDraw() { - observer.removeOnPreDrawListener(this); - root.getOverlay().add(mWallpaper); - - int[] location = new int[2]; - mWallpaper.getLocationOnScreen(location); - final int newY = location[1]; - - mWallpaper.setTranslationY(prevY - newY); - mWallpaper.animate() - .alpha(0f) - .setDuration(300) - .withEndAction(new Runnable() { - public void run() { - root.getOverlay().remove(mWallpaper); - mShadowFrame.addView(mWallpaper, 0); - mWallpaper.setVisibility(View.GONE); - } - }); - return true; - - } - }); - } - - private void animateWallpaperIn() { - mWallpaper.setVisibility(View.VISIBLE); - mWallpaper.setTranslationY(0); - mWallpaper.animate() - .alpha(1f) - .setDuration(300); - } - - protected String getAppliedFontPackageName() { - final Configuration config = getActivity().getResources().getConfiguration(); - final ThemeConfig themeConfig = config != null ? config.themeConfig : null; - return themeConfig != null ? themeConfig.getFontPkgName() : - ThemeConfig.getSystemTheme().getFontPkgName(); - } - - protected ThemeManager getThemeManager() { - return ThemeManager.getInstance(getActivity()); - } - - private void freeMediaPlayers() { - for (MediaPlayer mp : mMediaPlayers.values()) { - if (mp != null) { - mp.stop(); - mp.release(); - } - } - mMediaPlayers.clear(); - } - - protected View.OnClickListener mPlayPauseClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - MediaPlayer mp = (MediaPlayer) v.getTag(); - if (mp != null) { - if (mp.isPlaying()) { - ((ImageView) v).setImageResource(R.drawable.media_sound_preview); - mp.pause(); - mp.seekTo(0); - } else { - stopMediaPlayers(); - ((ImageView) v).setImageResource(R.drawable.media_sound_stop); - mp.start(); - } - } - } - }; - - protected MediaPlayer.OnCompletionListener mPlayCompletionListener - = new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - for (ImageView v : mMediaPlayers.keySet()) { - if (mp == mMediaPlayers.get(v)) { - if (v != null) { - v.setImageResource(R.drawable.media_sound_preview); - } - } - } - } - }; - - private void stopMediaPlayers() { - for (ImageView v : mMediaPlayers.keySet()) { - if (v != null) { - v.setImageResource(R.drawable.media_sound_preview); - } - MediaPlayer mp = mMediaPlayers.get(v); - if (mp != null && mp.isPlaying()) { - mp.pause(); - mp.seekTo(0); - } - } - } - - protected void resetTheme() { - mSelectedComponentsMap.clear(); - Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, mBaseThemePkgName); - args.putLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); - getLoaderManager().restartLoader(LOADER_ID_ALL, args, this); - mThemeResetting = true; - } - - @Override - public Loader onCreateLoader(int id, Bundle args) { - String pkgName = mPkgName; - long componentId = DEFAULT_COMPONENT_ID; - if (args != null) { - pkgName = args.getString(ARG_PACKAGE_NAME); - componentId = args.getLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); - } - return CursorLoaderHelper.themeFragmentCursorLoader(getActivity(), id, pkgName, - componentId); - } - - @Override - public void onLoadFinished(Loader loader, Cursor c) { - if (c.getCount() == 0) return; - mCurrentCursor = c; - mCurrentLoaderId = loader.getId(); - c.moveToFirst(); - boolean animate = !mApplyThemeOnPopulated; - switch (mCurrentLoaderId) { - case LOADER_ID_ALL: - if (mProcessingResources && !isThemeProcessing()) { - mProcessingResources = false; - hideProcessingOverlay(); - } - loadLegacyThemeInfo(c); - populateSupportedComponents(c); - loadWallpaper(c, false); - loadStatusBar(c, false); - loadIcons(c, false); - loadNavBar(c, false); - loadTitle(c); - loadFont(c, false); - mHandler.post(new Runnable() { - @Override - public void run() { - animateContentIn(); - } - }); - if (mShowLockScreenSelectorAfterContentLoaded) { - getChooserActivity().expandContentAndAnimateLockScreenCardIn(); - mShowLockScreenSelectorAfterContentLoaded = false; - } - break; - case LOADER_ID_STATUS_BAR: - loadStatusBar(c, animate); - break; - case LOADER_ID_FONT: - loadFont(c, animate); - break; - case LOADER_ID_ICONS: - loadIcons(c, animate); - break; - case LOADER_ID_WALLPAPER: - loadWallpaper(c, animate); - break; - case LOADER_ID_NAVIGATION_BAR: - loadNavBar(c, animate); - break; - case LOADER_ID_LIVE_LOCK_SCREEN: - case LOADER_ID_LOCKSCREEN: - loadLockScreen(c, animate); - break; - case LOADER_ID_STYLE: - loadStyle(c, animate); - break; - case LOADER_ID_BOOT_ANIMATION: - loadBootAnimation(c); - break; - case LOADER_ID_RINGTONE: - loadAudible(RingtoneManager.TYPE_RINGTONE, c, animate); - break; - case LOADER_ID_NOTIFICATION: - loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, animate); - break; - case LOADER_ID_ALARM: - loadAudible(RingtoneManager.TYPE_ALARM, c, animate); - break; - } - if (mCurrentLoaderId != LOADER_ID_ALL) { - if (!componentsChanged()) { - getChooserActivity().hideSaveApplyButton(); - } else if (!mApplyThemeOnPopulated) { - getChooserActivity().showSaveApplyButton(); - } - } - } - - @Override - public void onLoaderReset(Loader loader) {} - - private void loadAdditionalCards(Cursor c) { - for(int i=0; i < mAdditionalCards.getChildCount(); i++) { - View v = mAdditionalCards.getChildAt(i); - if (v instanceof ComponentCardView) { - String component = mCardIdsToComponentTypes.get(v.getId()); - loadAdditionalCard(c, component, shouldShowComponentCard(component)); - } - } - } - - private void loadAdditionalCard(Cursor c, String component, boolean hasContent) { - if (MODIFIES_LOCKSCREEN.equals(component)) { - if (hasContent) { - loadLockScreen(c, false); - } else { - mLockScreenCard.clearWallpaper(); - mLockScreenCard.setEmptyViewEnabled(true); - setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); - } - } else if (MODIFIES_LAUNCHER.equals(component)) { - // this was already loaded so no need to do this again. - } else if (MODIFIES_OVERLAYS.equals(component)) { - if (hasContent) { - loadStyle(c, false); - } else { - mStyleCard.setEmptyViewEnabled(true); - setAddComponentTitle(mStyleCard, - getString(R.string.style_label)); - } - } else if (MODIFIES_BOOT_ANIM.equals(component)) { - if (hasContent) { - loadBootAnimation(c); - } else { - mBootAnimationCard.setEmptyViewEnabled(true); - setAddComponentTitle(mBootAnimationCard, - getString(R.string.boot_animation_label)); - } - } else if (MODIFIES_RINGTONES.equals(component)) { - if (hasContent) { - loadAudible(RingtoneManager.TYPE_RINGTONE, c, false); - } else { - mRingtoneCard.setEmptyViewEnabled(true); - setAddComponentTitle(mRingtoneCard, - getAudibleLabel(RingtoneManager.TYPE_RINGTONE)); - } - } else if (MODIFIES_NOTIFICATIONS.equals(component)) { - if (hasContent) { - loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, false); - } else { - mNotificationCard.setEmptyViewEnabled(true); - setAddComponentTitle(mNotificationCard, - getAudibleLabel(RingtoneManager.TYPE_NOTIFICATION)); - } - } else if (MODIFIES_ALARMS.equals(component)) { - if (hasContent) { - loadAudible(RingtoneManager.TYPE_ALARM, c, false); - } else { - mAlarmCard.setEmptyViewEnabled(true); - setAddComponentTitle(mAlarmCard, - getAudibleLabel(RingtoneManager.TYPE_ALARM)); - } - } else { - throw new IllegalArgumentException("Don't know how to load: " + component); - } - } - - protected void populateSupportedComponents(Cursor c) { - List components = ThemeUtils.getAllComponents(); - for(String component : components) { - int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int modifiesCompIdx = c.getColumnIndex(component); - - String pkg = c.getString(pkgIdx); - boolean supported = (modifiesCompIdx >= 0) && (c.getInt(modifiesCompIdx) == 1); - if (supported) { - mBaseThemeSupportedComponents.add(component); - mSelectedComponentsMap.put(component, pkg); - } - } - - if (mApplyThemeOnPopulated) { - applyTheme(); - } - } - - /** - * Determines whether a card should be shown or not. - * UX Rules: - * 1) "My Theme" always shows all cards - * 2) Other themes only show what has been implemented in the theme - * - */ - protected Boolean shouldShowComponentCard(String component) { - String pkg = mSelectedComponentsMap.get(component); - return pkg != null && pkg.equals(mPkgName); - } - - protected void loadLegacyThemeInfo(Cursor c) { - int targetApiIdx = c.getColumnIndex(ThemesColumns.TARGET_API); - // If this is being called for a MyThemeFragment the index will be -1 so set to - // SYSTEM_TARGET_API so we don't display the tag. If the user applied a legacy theme - // then they should have already been warned. - int targetApi = targetApiIdx < 0 ? SYSTEM_TARGET_API : c.getInt(targetApiIdx); - mIsLegacyTheme = targetApi != SYSTEM_TARGET_API && targetApi <= Build.VERSION_CODES.KITKAT; - mThemeTagLayout.setLegacyTagEnabled(mIsLegacyTheme); - } - - protected void loadTitle(Cursor c) { - int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); - int authorIdx = c.getColumnIndex(ThemesColumns.AUTHOR); - mTitle.setText(c.getString(titleIdx)); - mAuthor.setText(c.getString(authorIdx)); - } - - protected void loadWallpaper(Cursor c, boolean animate) { - mExternalWallpaperUri = null; - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mWallpaperCard, true); - } - if (mWallpaperCard.isShowingEmptyView()) mWallpaperCard.setEmptyViewEnabled(false); - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int wpIdx = c.getColumnIndex(PreviewColumns.WALLPAPER_PREVIEW); - int cmpntIdIdx = c.getColumnIndex(PreviewColumns.COMPONENT_ID); - final Resources res = getResources(); - Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx); - if (bitmap != null) { - mWallpaper.setImageBitmap(bitmap); - mWallpaperCard.setWallpaper(new BitmapDrawable(res, bitmap)); - String pkgName = c.getString(pkgNameIdx); - Long cmpntId = (cmpntIdIdx >= 0) ? c.getLong(cmpntIdIdx) : DEFAULT_COMPONENT_ID; - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_LAUNCHER))) { - mSelectedComponentsMap.put(MODIFIES_LAUNCHER, pkgName); - mSelectedWallpaperComponentId = cmpntId; - setCardTitle(mWallpaperCard, pkgName, getString(R.string.wallpaper_label)); - } - } else { - // Set the wallpaper to "None" - mWallpaperCard.setWallpaper(null); - setCardTitle(mWallpaperCard, WALLPAPER_NONE, getString(R.string.wallpaper_label)); - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, WALLPAPER_NONE); - } - - if (animate) { - animateContentChange(R.id.wallpaper_card, mWallpaperCard, overlay); - } - } - - protected void loadLockScreen(Cursor c, boolean animate) { - mExternalLockscreenUri = null; - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mLockScreenCard, true); - } - if (mLockScreenCard.isShowingEmptyView()) mLockScreenCard.setEmptyViewEnabled(false); - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int liveLockIndex = c.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN); - boolean isLiveLockScreen = liveLockIndex >= 0 && c.getInt(liveLockIndex) == 1; - - int wpIdx = isLiveLockScreen - ? c.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW) - : c.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_PREVIEW); - final Resources res = getResources(); - Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx); - if (bitmap != null) { - mLockScreenCard.setWallpaper(new BitmapDrawable(res, bitmap)); - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && (mBaseThemeSupportedComponents.contains(MODIFIES_LOCKSCREEN) || - mBaseThemeSupportedComponents.contains(MODIFIES_LIVE_LOCK_SCREEN)))) { - if (isLiveLockScreen) { - mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, pkgName); - if (mCurrentTheme.containsKey(MODIFIES_LOCKSCREEN)) { - mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); - } else { - mSelectedComponentsMap.remove(MODIFIES_LOCKSCREEN); - } - setCardTitle(mLockScreenCard, pkgName, - getString(R.string.live_lock_screen_label)); - } else { - mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, pkgName); - if (mCurrentTheme.containsKey(MODIFIES_LIVE_LOCK_SCREEN)) { - mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); - } else { - mSelectedComponentsMap.remove(MODIFIES_LIVE_LOCK_SCREEN); - } - setCardTitle(mLockScreenCard, pkgName, getString(R.string.lockscreen_label)); - } - } - } else { - // Set the lockscreen wallpaper to "None" - mLockScreenCard.setWallpaper(null); - setCardTitle(mLockScreenCard, WALLPAPER_NONE, getString(R.string.lockscreen_label)); - } - - if (animate) { - animateContentChange(R.id.lockscreen_card, mLockScreenCard, overlay); - } - } - - protected void loadStatusBar(Cursor c, boolean animate) { - int backgroundIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); - int wifiIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_ICON); - int wifiMarginIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END); - int bluetoothIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BLUETOOTH_ICON); - int signalIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_SIGNAL_ICON); - int batteryIdx = c.getColumnIndex(Utils.getBatteryIndex(mBatteryStyle)); - int clockColorIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR); - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - - Bitmap background = Utils.loadBitmapBlob(c, backgroundIdx); - Bitmap bluetoothIcon = Utils.loadBitmapBlob(c, bluetoothIdx); - Bitmap wifiIcon = Utils.loadBitmapBlob(c, wifiIdx); - Bitmap signalIcon = Utils.loadBitmapBlob(c, signalIdx); - Bitmap batteryIcon = Utils.loadBitmapBlob(c, batteryIdx); - int wifiMargin = wifiMarginIdx != -1 ? c.getInt(wifiMarginIdx) : DEFAULT_WIFI_MARGIN; - int clockTextColor = clockColorIdx != -1 ? c.getInt(clockColorIdx) : DEFAULT_CLOCK_COLOR; - - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mStatusBar, false); - } - if (mStatusBarCard.isShowingEmptyView()) mStatusBarCard.setEmptyViewEnabled(false); - - mStatusBar.setBackground(new BitmapDrawable(getActivity().getResources(), background)); - mBluetooth.setImageBitmap(bluetoothIcon); - mWifi.setImageBitmap(wifiIcon); - mSignal.setImageBitmap(signalIcon); - mBattery.setImageBitmap(batteryIcon); - mClock.setTextColor(clockTextColor); - - ViewGroup.MarginLayoutParams params = - (ViewGroup.MarginLayoutParams) mWifi.getLayoutParams(); - params.setMarginEnd(wifiMargin); - mWifi.setLayoutParams(params); - - if (mBatteryStyle == 4) { - mBattery.setVisibility(View.GONE); - } else { - mBattery.setVisibility(View.VISIBLE); - } - mStatusBar.post(new Runnable() { - @Override - public void run() { - mStatusBar.invalidate(); - } - }); - if (pkgNameIdx > -1) { - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_STATUS_BAR))) { - mSelectedComponentsMap.put(MODIFIES_STATUS_BAR, pkgName); - setCardTitle(mStatusBarCard, pkgName, - getString(R.string.statusbar_label)); - } - } - if (animate) { - animateContentChange(R.id.status_bar_container, mStatusBar, overlay); - } - } - - protected void loadIcons(Cursor c, boolean animate) { - if (mIconCard.isShowingEmptyView()) { - mIconCard.setEmptyViewEnabled(false); - } - int[] iconIdx = new int[3]; - iconIdx[0] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_1); - iconIdx[1] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_2); - iconIdx[2] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_3); - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - - // Set the icons. If the provider does not have an icon preview then - // fall back to the default icon set - IconPreviewHelper helper = new IconPreviewHelper(getActivity(), ""); - ViewGroup iconContainer = - (ViewGroup) mIconCard.findViewById(R.id.icon_preview_container); - int numOfChildren = iconContainer.getChildCount(); - - List iconViews = new ArrayList(numOfChildren); - for(int i=0; i < numOfChildren; i++) { - final View view = iconContainer.getChildAt(i); - if (!(view instanceof ImageView)) continue; - iconViews.add((ImageView) view); - } - - for(int i=0; i < iconViews.size() && i < iconIdx.length; i++) { - final ImageView v = iconViews.get(i); - Bitmap bitmap = Utils.loadBitmapBlob(c, iconIdx[i]); - Drawable oldIcon = v.getDrawable(); - Drawable newIcon; - if (bitmap == null) { - ComponentName component = sIconComponents[i]; - newIcon = helper.getDefaultIcon(component.getPackageName(), - component.getClassName()); - } else { - newIcon = new BitmapDrawable(getResources(), bitmap); - } - if (animate) { - Drawable[] layers = new Drawable[2]; - layers[0] = oldIcon instanceof IconTransitionDrawable ? - ((IconTransitionDrawable) oldIcon).getDrawable(1) : oldIcon; - layers[1] = newIcon; - final IconTransitionDrawable itd = new IconTransitionDrawable(layers); - v.postDelayed(new Runnable() { - @Override - public void run() { - itd.startTransition(ANIMATE_COMPONENT_CHANGE_DURATION); - v.setImageDrawable(itd); - } - }, ANIMATE_COMPONENT_ICON_DELAY * i); - } else { - v.setImageDrawable(newIcon); - } - } - if (pkgNameIdx > -1) { - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_ICONS))) { - mSelectedComponentsMap.put(MODIFIES_ICONS, pkgName); - setCardTitle(mIconCard, pkgName, - getString(R.string.icon_label)); - } - } - } - - protected void loadNavBar(Cursor c, boolean animate) { - int backButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_BACK_BUTTON); - int homeButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_HOME_BUTTON); - int recentButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_RECENT_BUTTON); - int backgroundIdx = c.getColumnIndex(PreviewColumns.NAVBAR_BACKGROUND); - if (backgroundIdx == -1) { - backgroundIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); - } - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - - Bitmap background = Utils.loadBitmapBlob(c, backgroundIdx); - Bitmap backButton = Utils.loadBitmapBlob(c, backButtonIdx); - Bitmap homeButton = Utils.loadBitmapBlob(c, homeButtonIdx); - Bitmap recentButton = Utils.loadBitmapBlob(c, recentButtonIdx); - - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mNavBar, false); - } - if (mNavBarCard.isShowingEmptyView()) mNavBarCard.setEmptyViewEnabled(false); - - mNavBar.setBackground(new BitmapDrawable(getActivity().getResources(), background)); - mBackButton.setImageBitmap(backButton); - mHomeButton.setImageBitmap(homeButton); - mRecentButton.setImageBitmap(recentButton); - - if (pkgNameIdx > -1) { - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_NAVIGATION_BAR))) { - mSelectedComponentsMap.put(MODIFIES_NAVIGATION_BAR, pkgName); - setCardTitle(mNavBarCard, pkgName, getString(R.string.navbar_label)); - } - } - if (animate) { - animateContentChange(R.id.navigation_bar_container, mNavBar, overlay); - } - } - - protected void loadFont(Cursor c, boolean animate) { - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mFontPreview, true); - } - if (mFontCard.isShowingEmptyView()) mFontCard.setEmptyViewEnabled(false); - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - String pkgName = pkgNameIdx >= 0 ? c.getString(pkgNameIdx) : mPkgName; - TypefaceHelperCache cache = TypefaceHelperCache.getInstance(); - ThemedTypefaceHelper helper = cache.getHelperForTheme(getActivity(), pkgName); - mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); - mFontPreview.setTypeface(mTypefaceNormal); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_FONTS))) { - mSelectedComponentsMap.put(MODIFIES_FONTS, pkgName); - setCardTitle(mFontCard, pkgName, getString(R.string.font_label)); - } - - if (animate) { - animateContentChange(R.id.font_preview_container, mFontPreview, overlay); - } - } - - protected void loadStyle(Cursor c, boolean animate) { - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(mStylePreview, true); - } - if (mStyleCard.isShowingEmptyView()) { - mStyleCard.setEmptyViewEnabled(false); - } - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int styleIdx = c.getColumnIndex(PreviewColumns.STYLE_PREVIEW); - mStylePreview.setImageBitmap(Utils.loadBitmapBlob(c, styleIdx)); - if (pkgNameIdx > -1) { - String pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_OVERLAYS))) { - mSelectedComponentsMap.put(MODIFIES_OVERLAYS, pkgName); - setCardTitle(mStyleCard, pkgName, - getString(R.string.style_label)); - } - } - if (animate) { - animateContentChange(R.id.style_card, mStylePreview, overlay); - } - } - - protected void loadBootAnimation(Cursor c) { - if (mBootAnimationCard.isShowingEmptyView()) { - mBootAnimationCard.setEmptyViewEnabled(false); - } - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - if (mBootAnimation != null) { - String pkgName; - if (pkgNameIdx > -1) { - pkgName = c.getString(pkgNameIdx); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(MODIFIES_BOOT_ANIM))) { - mSelectedComponentsMap.put(MODIFIES_BOOT_ANIM, pkgName); - setCardTitle(mBootAnimationCard, pkgName, - getString(R.string.boot_animation_label)); - } - } else { - pkgName = mCurrentTheme.get(MODIFIES_BOOT_ANIM); - } - mBootAnimation.stop(); - new AnimationLoader(getActivity(), pkgName, mBootAnimation).execute(); - } - } - - protected void loadAudible(int type, Cursor c, boolean animate) { - ComponentCardView audibleContainer = null; - ImageView playPause = null; - String component = null; - int parentResId = 0; - switch (type) { - case RingtoneManager.TYPE_RINGTONE: - audibleContainer = mRingtoneCard; - playPause = mRingtonePlayPause; - component = MODIFIES_RINGTONES; - parentResId = R.id.ringtone_preview_container; - break; - case RingtoneManager.TYPE_NOTIFICATION: - audibleContainer = mNotificationCard; - playPause = mNotificationPlayPause; - component = MODIFIES_NOTIFICATIONS; - parentResId = R.id.notification_preview_container; - break; - case RingtoneManager.TYPE_ALARM: - audibleContainer = mAlarmCard; - playPause = mAlarmPlayPause; - component = MODIFIES_ALARMS; - parentResId = R.id.alarm_preview_container; - break; - } - if (audibleContainer == null) return; - - View content = audibleContainer.findViewById(R.id.content); - Drawable overlay = null; - if (animate) { - overlay = getOverlayDrawable(content, true); - } - if (audibleContainer.isShowingEmptyView()) { - audibleContainer.setEmptyViewEnabled(false); - } - - int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); - int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); - if (playPause == null) { - playPause = (ImageView) audibleContainer.findViewById(R.id.play_pause); - } - TextView title = (TextView) audibleContainer.findViewById(R.id.audible_name); - MediaPlayer mp = mMediaPlayers.get(playPause); - if (mp == null) { - mp = new MediaPlayer(); - } - String pkgName = c.getString(pkgNameIdx); - setCardTitle(audibleContainer, pkgName, getAudibleLabel(type)); - AudibleLoadingThread thread = new AudibleLoadingThread(getActivity(), type, pkgName, mp); - title.setText(c.getString(titleIdx)); - if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) - && mBaseThemeSupportedComponents.contains(component))) { - mSelectedComponentsMap.put(component, pkgName); - } - - playPause.setVisibility(View.VISIBLE); - playPause.setTag(mp); - mMediaPlayers.put(playPause, mp); - playPause.setOnClickListener(mPlayPauseClickListener); - mp.setOnCompletionListener(mPlayCompletionListener); - if (animate) { - animateContentChange(parentResId, content, overlay); - } - thread.start(); - } - - protected Drawable getOverlayDrawable(View v, boolean requiresTransparency) { - if (!v.isDrawingCacheEnabled()) v.setDrawingCacheEnabled(true); - Bitmap cache = v.getDrawingCache(true).copy( - requiresTransparency ? Config.ARGB_8888 : Config.RGB_565, false); - Drawable d = cache != null ? new BitmapDrawable(getResources(), cache) : null; - v.destroyDrawingCache(); - - return d; - } - - protected String getAudibleLabel(int type) { - switch (type) { - case RingtoneManager.TYPE_RINGTONE: - return getString(R.string.ringtone_label); - case RingtoneManager.TYPE_NOTIFICATION: - return getString(R.string.notification_label); - case RingtoneManager.TYPE_ALARM: - return getString(R.string.alarm_label); - } - return null; - } - - protected void setCardTitle(ComponentCardView card, String pkgName, String title) { - TextView tv = (TextView) card.findViewById(R.id.label); - if (Utils.getDefaultThemePackageName(getActivity()).equals(pkgName)) { - tv.setText(getString(R.string.default_tag_text) + " " + title); - } else { - tv.setText(title); - } - } - - protected void setAddComponentTitle(ComponentCardView card, String title) { - TextView tv = (TextView) card.findViewById(R.id.label); - tv.setText(getString(R.string.add_component_text) + " " + title); - } - - public static ComponentName[] getIconComponents(Context context) { - if (sIconComponents == null || sIconComponents.length == 0) { - sIconComponents = new ComponentName[]{COMPONENT_DIALER, COMPONENT_MESSAGING, - COMPONENT_CAMERA, COMPONENT_BROWSER}; - - PackageManager pm = context.getPackageManager(); - - // if device does not have telephony replace dialer and mms - if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { - sIconComponents[0] = COMPONENT_CALENDAR; - sIconComponents[1] = COMPONENT_GALERY; - } else { - // decide on which dialer icon to use - try { - if (pm.getPackageInfo(DIALER_NEXT_PACKAGE, 0) != null) { - sIconComponents[0] = COMPONENT_DIALERNEXT; - } - } catch (PackageManager.NameNotFoundException e) { - // default to COMPONENT_DIALER - } - } - - if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) { - sIconComponents[2] = COMPONENT_SETTINGS; - } else { - // decide on which camera icon to use - try { - if (pm.getPackageInfo(CAMERA_NEXT_PACKAGE, 0) != null) { - sIconComponents[2] = COMPONENT_CAMERANEXT; - } - } catch (PackageManager.NameNotFoundException e) { - // default to COMPONENT_CAMERA - } - } - - } - return sIconComponents; - } - - private void setupCardClickListeners(View parent) { - for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { - parent.findViewById(mCardIdsToComponentTypes.keyAt(i)) - .setOnClickListener(mCardClickListener); - } - } - - private View.OnClickListener mCardClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (isShowingConfirmCancelOverlay() || isShowingCustomizeResetLayout()) return; - if (mActiveCardId > 0) { - // need to fade the newly selected card in if another was currently selected. - ((ComponentCardView) v).animateCardFadeIn(); - } - mActiveCardId = v.getId(); - String component = mCardIdsToComponentTypes.get(mActiveCardId); - // Only pass on mSelectedWallpaperComponentId if dealing with mods_launcher - long selectedComponentId = (ThemesColumns.MODIFIES_LAUNCHER.equals(component)) ? - mSelectedWallpaperComponentId : DEFAULT_COMPONENT_ID; - String pkgName = mSelectedComponentsMap.get(component); - if (component.equals(MODIFIES_LOCKSCREEN) && TextUtils.isEmpty(pkgName)) { - String liveLockScreenPkg = mSelectedComponentsMap.get(MODIFIES_LIVE_LOCK_SCREEN); - if (liveLockScreenPkg != null) { - pkgName = liveLockScreenPkg; - } - } - getChooserActivity().showComponentSelector(component, pkgName, selectedComponentId, v); - fadeOutNonSelectedCards(mActiveCardId); - stopMediaPlayers(); - } - }; - - private ConfirmCancelOverlay.OnOverlayDismissedListener mApplyCancelListener = - new ConfirmCancelOverlay.OnOverlayDismissedListener() { - @Override - public void onDismissed(boolean accepted) { - hideConfirmCancelOverlay(accepted); - } - }; - - private ConfirmCancelOverlay.OnOverlayDismissedListener mDeleteConfirmationListener = - new ConfirmCancelOverlay.OnOverlayDismissedListener() { - @Override - public void onDismissed(boolean accepted) { - if (accepted) uninstallTheme(); - hideConfirmCancelOverlay(); - } - }; - - private ConfirmCancelOverlay.OnOverlayDismissedListener mResetConfirmationListener = - new ConfirmCancelOverlay.OnOverlayDismissedListener() { - @Override - public void onDismissed(boolean accepted) { - if (accepted) resetTheme(); - hideConfirmCancelOverlay(); - } - }; - - private View.OnClickListener mCustomizeResetClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - if (v == mDismissButton) { - hideCustomizeResetLayout(CustomizeResetAction.Dismiss); - } else if (v == mResetButton) { - hideCustomizeResetLayout(CustomizeResetAction.Reset); - } else if (v == mCustomizeButton) { - hideCustomizeResetLayout(CustomizeResetAction.Customize); - } - } - }; - - protected void loadComponentFromPackage(String pkgName, String component, long componentId) { - Bundle args = new Bundle(); - args.putString(ARG_PACKAGE_NAME, pkgName); - args.putLong(ARG_COMPONENT_ID, componentId); - int loaderId = LOADER_ID_INVALID; - if (MODIFIES_STATUS_BAR.equals(component)) { - loaderId = LOADER_ID_STATUS_BAR; - } else if (MODIFIES_FONTS.equals(component)) { - loaderId = LOADER_ID_FONT; - } else if (MODIFIES_ICONS.equals(component)) { - loaderId = LOADER_ID_ICONS; - } else if (MODIFIES_NAVIGATION_BAR.equals(component)) { - loaderId = LOADER_ID_NAVIGATION_BAR; - } else if (MODIFIES_LAUNCHER.equals(component)) { - if (pkgName != null) { - if (TextUtils.isEmpty(pkgName)) { - mWallpaperCard.setWallpaper(null); - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, WALLPAPER_NONE); - setCardTitle(mWallpaperCard, WALLPAPER_NONE, - getString(R.string.wallpaper_label)); - getChooserActivity().showSaveApplyButton(); - } else if (ComponentSelector.EXTERNAL_WALLPAPER.equals(pkgName)) { - // Check if we have READ_EXTERNAL_STORAGE permission and if not request it, - // otherwise let the user pick an image - if (getActivity().checkSelfPermission( - READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { - mAfterPermissionGrantedRunnable = new Runnable() { - @Override - public void run() { - getChooserActivity().pickExternalWallpaper(); - setCardTitle(mWallpaperCard, WALLPAPER_NONE, - getString(R.string.wallpaper_label)); - } - }; - requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, - PERMISSION_REQUEST); - } else { - getChooserActivity().pickExternalWallpaper(); - setCardTitle(mWallpaperCard, WALLPAPER_NONE, - getString(R.string.wallpaper_label)); - } - } else { - loaderId = LOADER_ID_WALLPAPER; - } - } - } else if (MODIFIES_LOCKSCREEN.equals(component) - || MODIFIES_LIVE_LOCK_SCREEN.equals(component)) { - if (pkgName != null && TextUtils.isEmpty(pkgName)) { - mLockScreenCard.setWallpaper(null); - mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); - - if(mSelectedComponentsMap.containsKey(MODIFIES_LIVE_LOCK_SCREEN)) { - mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); - } - setCardTitle(mLockScreenCard, WALLPAPER_NONE, - getString(R.string.lockscreen_label)); - if (mLockScreenCard.isShowingEmptyView()) { - mLockScreenCard.setEmptyViewEnabled(false); - } - getChooserActivity().showSaveApplyButton(); - } else if (ComponentSelector.EXTERNAL_WALLPAPER.equals(pkgName)) { - // Check if we have READ_EXTERNAL_STORAGE permission and if not request it, - // otherwise let the user pick an image - if (getActivity().checkSelfPermission( - READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { - mAfterPermissionGrantedRunnable = new Runnable() { - @Override - public void run() { - getChooserActivity().pickExternalLockscreen(); - setCardTitle(mLockScreenCard, WALLPAPER_NONE, - getString(R.string.lockscreen_label)); - } - }; - requestPermissions(new String[] {READ_EXTERNAL_STORAGE}, - PERMISSION_REQUEST); - } else { - getChooserActivity().pickExternalLockscreen(); - setCardTitle(mLockScreenCard, WALLPAPER_NONE, - getString(R.string.lockscreen_label)); - } - } else if (ComponentSelector.MOD_LOCK.equals(pkgName)) { - startLiveLockScreenSettings(); - } else { - if (MODIFIES_LIVE_LOCK_SCREEN.equals(component)) { - loaderId = LOADER_ID_LIVE_LOCK_SCREEN; - } else { - loaderId = LOADER_ID_LOCKSCREEN; - } - } - } else if (MODIFIES_OVERLAYS.equals(component)) { - loaderId = LOADER_ID_STYLE; - } else if (MODIFIES_BOOT_ANIM.equals(component)) { - loaderId = LOADER_ID_BOOT_ANIMATION; - } else if (MODIFIES_RINGTONES.equals(component)) { - loaderId = LOADER_ID_RINGTONE; - } else if (MODIFIES_NOTIFICATIONS.equals(component)) { - loaderId = LOADER_ID_NOTIFICATION; - } else if (MODIFIES_ALARMS.equals(component)) { - loaderId = LOADER_ID_ALARM; - } else { - return; - } - - if (loaderId != LOADER_ID_INVALID) { - getLoaderManager().restartLoader(loaderId, args, ThemeFragment.this); - } - } - - private OnItemClickedListener mOnComponentItemClicked = new OnItemClickedListener() { - @Override - public void onItemClicked(String pkgName, long componentId, Bundle params) { - String component = mSelector.getComponentType(); - if (MODIFIES_LOCKSCREEN.equals(component) && params != null) { - boolean isLiveLockView = params.getBoolean( - ComponentSelector.IS_LIVE_LOCK_SCREEN_VIEW,false); - if (isLiveLockView) { - //We got here because an live lock thubmnail view was clicked. We need to - //replace the component to load the proper data from the provider. - component = MODIFIES_LIVE_LOCK_SCREEN; - } - } - loadComponentFromPackage(pkgName, component, componentId); - } - }; - - private void fadeOutNonSelectedCards(int selectedCardId) { - for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { - if (mCardIdsToComponentTypes.keyAt(i) != selectedCardId) { - ComponentCardView card = (ComponentCardView) getView().findViewById( - mCardIdsToComponentTypes.keyAt(i)); - if (card != null) card.animateCardFadeOut(); - } - } - } - - protected void animateContentChange(int parentId, View viewToAnimate, Drawable overlay) { - ((ComponentCardView) getView().findViewById(parentId)) - .animateContentChange(viewToAnimate, overlay, ANIMATE_COMPONENT_CHANGE_DURATION); - } - - private Runnable mApplyThemeRunnable = new Runnable() { - @Override - public void run() { - final Context context = getActivity(); - if (context != null) { - // Post this on mHandler so the client is added and removed from the same - // thread - mHandler.post(new Runnable() { - @Override - public void run() { - final Map componentsToApply = getComponentsToApply(); - if (componentsToApply != null && componentsToApply.size() > 0) { - final Map fullMap - = fillMissingComponentsWithDefault(componentsToApply); - ThemeManager tm = getThemeManager(); - if (tm != null) { - try { - tm.addClient(ThemeFragment.this); - } catch (IllegalArgumentException e) { - /* ignore since this means we already have a listener added */ - } - ThemeChangeRequest request = - getThemeChangeRequestForComponents(fullMap); - boolean value = request.getReqeustType(). - equals(RequestType.USER_REQUEST_MIXNMATCH); - - tm.requestThemeChange(request, !value); - } - mApplyThemeOnPopulated = false; - } else { - onFinish(true); - } - } - }); - } - } - }; - - protected Map fillMissingComponentsWithDefault( - Map originalMap) { - HashMap newMap = new HashMap(); - newMap.putAll(originalMap); - Map defaultMap = getEmptyComponentsMap(); - for(Map.Entry entry : defaultMap.entrySet()) { - String component = entry.getKey(); - String defaultPkg = entry.getValue(); - if (!newMap.containsKey(component)) { - newMap.put(component, defaultPkg); - } - } - return newMap; - } - - protected Map getEmptyComponentsMap() { - List componentsList = ThemeUtils.getAllComponents(); - Map defaultMap = new HashMap<>(componentsList.size()); - for (String component : componentsList) { - defaultMap.put(component, ""); - } - return defaultMap; - } - - /** - * This is the method that will be called when applying a theme and the idea is to override - * it in MyThemeFragment and pass in a different RequestType, once we have a type that indicates - * the user is mixing and matching instead of applying an entire theme. - * @param componentMap - * @return - */ - protected ThemeChangeRequest getThemeChangeRequestForComponents( - Map componentMap) { - return getThemeChangeRequestForComponents(componentMap, RequestType.USER_REQUEST); - } - - protected ThemeChangeRequest getThemeChangeRequestForComponents( - Map componentMap, RequestType requestType) { - ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); - for (String component : componentMap.keySet()) { - builder.setComponent(component, componentMap.get(component)); - } - builder.setRequestType(requestType); - if (mThemeVersion >= 3) { - builder.setWallpaperId(mSelectedWallpaperComponentId != null - ? mSelectedWallpaperComponentId - : DEFAULT_COMPONENT_ID); - } - return builder.build(); - } - - protected Map getComponentsToApply() { - return mSelectedComponentsMap; - } - - private Runnable mApplyExternalWallpaperRunnable = new Runnable() { - @Override - public void run() { - // If an external image was selected for the wallpaper, we need to - // set that manually. - if (mExternalWallpaperUri != null) { - WallpaperManager wm = - WallpaperManager.getInstance(getActivity()); - final Context context = getActivity(); - final Resources res = context.getResources(); - final Point size = new Point(wm.getDesiredMinimumWidth(), - wm.getDesiredMinimumHeight()); - Bitmap bmp = WallpaperUtils.createPreview(size, context, mExternalWallpaperUri, - null, res, 0, 0, false); - try { - wm.setBitmap(bmp); - } catch (Exception e) { - Log.e(TAG, "Unable to set external wallpaper", e); - } - } - } - }; - - private Runnable mApplyExternalLockscreenRunnable = new Runnable() { - @Override - public void run() { - // If an external image was selected for the wallpaper, we need to - // set that manually. - if (mExternalLockscreenUri != null) { - WallpaperManager wm = - WallpaperManager.getInstance(getActivity()); - final Context context = getActivity(); - final Resources res = context.getResources(); - final Point size = new Point(); - ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(size); - Bitmap bmp = WallpaperUtils.createPreview(size, context, mExternalLockscreenUri, - null, res, 0, 0, false); - try { - wm.setKeyguardBitmap(bmp); - } catch (Exception e) { - Log.e(TAG, "Unable to set external lockscreen wallpaper", e); - } - } - } - }; - - class RestoreLockScreenCardRunnable implements Runnable { - - final private boolean mWasShowingNone; - private String mCurrentLockScreenPkgName; - - public RestoreLockScreenCardRunnable(boolean w, String pkgName) { - mWasShowingNone = w; - mCurrentLockScreenPkgName = pkgName; - } - - @Override - public void run() { - if (!TextUtils.isEmpty(mCurrentLockScreenPkgName)) { - loadComponentFromPackage(mCurrentLockScreenPkgName, - MODIFIES_LOCKSCREEN, 0); - } else if (mWasShowingNone) { - mLockScreenCard.setWallpaper(null); - TextView none = (TextView) mLockScreenCard.findViewById( - R.id.none); - if (none != null) { - none.setVisibility(View.VISIBLE); - } - mLockScreenCard.setEmptyViewEnabled(false); - } else { - mLockScreenCard.clearWallpaper(); - TextView none = (TextView) mLockScreenCard.findViewById( - R.id.none); - if (none != null) { - none.setVisibility(View.GONE); - } - mLockScreenCard.setEmptyViewEnabled(true); - setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); - } - } - } - - protected void applyTheme() { - if (mExternalWallpaperUri == null && mExternalLockscreenUri == null && - (mSelectedComponentsMap == null || mSelectedComponentsMap.size() <= 0)) { - return; - } - final Map componentsToApply = getComponentsToApply(); - boolean isLLSEnabled = CMSettings.Secure.getInt(getActivity().getContentResolver(), - LIVE_LOCK_SCREEN_ENABLED, 0) == 1; - if (!TextUtils.isEmpty(componentsToApply.get(MODIFIES_LIVE_LOCK_SCREEN)) && !isLLSEnabled) { - AlertDialog d = new AlertDialog.Builder(getActivity(), - android.R.style.Theme_Material_Dialog) - .setTitle(R.string.enable_live_lock_screen_dialog_title) - .setMessage(R.string.enable_live_lock_screen_dialog_message) - .setPositiveButton(R.string.enable_live_lock_screen_dialog_positive_btn_text, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - CMSettings.Secure.putInt(getActivity().getContentResolver(), - LIVE_LOCK_SCREEN_ENABLED, 1); - getChooserActivity().themeChangeStart(); - animateProgressIn(mApplyThemeRunnable); - } - }) - .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mSelectedComponentsMap.remove(MODIFIES_LIVE_LOCK_SCREEN); - boolean wasNone = false; - if (TextUtils.equals("", mSelectedComponentsMap.get( - MODIFIES_LOCKSCREEN))) { - if (!TextUtils.isEmpty(mCurrentTheme.get(MODIFIES_LOCKSCREEN))) { - //The map entry was set to empty string because there's a - //lockscreen currently applied and setting this entry to empty - //would instruct the ThemeManager to clear the currently applied - //lockscreen, but the user decided not to enable LLS so we need - //to abort this change - mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, - mCurrentTheme.get(MODIFIES_LOCKSCREEN)); - } else { - wasNone = true; - } - } - //Restore the lockscreen card to its previous state - mHandler.post(new RestoreLockScreenCardRunnable(wasNone, - mCurrentTheme.get(MODIFIES_LOCKSCREEN))); - - //Did the user make more changes? - boolean modLLS = componentsToApply.containsKey( - MODIFIES_LIVE_LOCK_SCREEN); - boolean modLockscreen = componentsToApply.containsKey( - MODIFIES_LOCKSCREEN); - if (modLLS && ((modLockscreen && componentsToApply.size() > 2) || - (!modLockscreen && componentsToApply.size() > 1))) { - getChooserActivity().themeChangeStart(); - animateProgressIn(mApplyThemeRunnable); - } - } - }) - .setCancelable(false) - .create(); - d.setCanceledOnTouchOutside(false); - d.show(); - } else { - getChooserActivity().themeChangeStart(); - animateProgressIn(mApplyThemeRunnable); - } - } - - /** - * Use when applyTheme() might be too early. ie mSelectedComponentsMap is not pop. yet - * @param pkgName Only used in MyThemeFragment to apply components on top of current theme - * @param components Optional list of components to apply. - */ - protected void applyThemeWhenPopulated(String pkgName, List components) { - mApplyThemeOnPopulated = true; - } - - private void animateProgressIn(Runnable endAction) { - mProgress.setVisibility(View.VISIBLE); - mProgress.setProgress(0); - float pivotX = mTitleLayout.getWidth() - - getResources().getDimensionPixelSize(R.dimen.apply_progress_padding); - ScaleAnimation scaleAnim = new ScaleAnimation(0f, 1f, 1f, 1f, - pivotX, 0f); - scaleAnim.setDuration(ANIMATE_PROGRESS_IN_DURATION); - - mTitleLayout.animate() - .translationXBy(-(pivotX / 3)) - .alpha(0f) - .setDuration(ANIMATE_TITLE_OUT_DURATION) - .setInterpolator(new AccelerateInterpolator()) - .withEndAction(endAction).start(); - mProgress.startAnimation(scaleAnim); - } - - private void animateProgressOut() { - mProgress.setVisibility(View.VISIBLE); - float pivotX = mTitleLayout.getWidth() - - getResources().getDimensionPixelSize(R.dimen.apply_progress_padding); - ScaleAnimation scaleAnim = new ScaleAnimation(1f, 0f, 1f, 1f, - pivotX, 0f); - scaleAnim.setDuration(ANIMATE_PROGRESS_OUT_DURATION); - scaleAnim.setFillAfter(false); - scaleAnim.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - mProgress.setVisibility(View.GONE); - if (mThemeResetting) { - mThemeResetting = false; - mThemeTagLayout.setCustomizedTagEnabled(false); - } - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - - mTitleLayout.animate() - .translationXBy((pivotX / 3)) - .alpha(1f) - .setDuration(ANIMATE_TITLE_IN_DURATION) - .setInterpolator(new AccelerateInterpolator()) - .start(); - mProgress.startAnimation(scaleAnim); - if (mThemeResetting) mReset.setVisibility(View.GONE); - } - - private void animateContentIn() { - if (mSkipLoadingAnim) { - return; - } - AnimatorSet set = new AnimatorSet(); - set.setDuration(ANIMATE_TITLE_IN_DURATION); - set.play(ObjectAnimator.ofFloat(mLoadingView, "alpha", 1f, 0f)) - .with(ObjectAnimator.ofFloat(mTitleLayout, "alpha", 0f, 1f)); - set.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mLoadingView.setVisibility(View.GONE); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - set.start(); - } - - private void disableActionButtons() { - mCustomize.setEnabled(false); - mDelete.setEnabled(false); - mReset.setEnabled(false); - } - - private void enableActionButtons() { - mCustomize.setEnabled(true); - mDelete.setEnabled(true); - mReset.setEnabled(true); - } - - public boolean isShowingConfirmCancelOverlay() { - return mConfirmCancelOverlay.getVisibility() == View.VISIBLE; - } - - public void showApplyThemeOverlay() { - if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; - mConfirmCancelOverlay.setTitle(R.string.apply_theme_overlay_title); - mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() - .getColor(R.color.apply_overlay_background)); - mConfirmCancelOverlay.setOnOverlayDismissedListener(mApplyCancelListener); - getChooserActivity().lockPager(); - ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); - mConfirmCancelOverlay.setVisibility(View.VISIBLE); - mConfirmCancelOverlay.setAlpha(0f); - anim.setListener(null); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(1f).start(); - - if (mIsLegacyTheme) { - // Display cm11 theme warning message - TextView tv = (TextView) mConfirmCancelOverlay.findViewById(R.id.warning_message); - tv.setVisibility(View.VISIBLE); - tv.setText(String.format(getString(R.string.legacy_theme_warning), mTitle.getText())); - } else if (Utils.hasPerAppThemesApplied(getActivity())) { - // Display per app theme changes will be removed warning - TextView tv = (TextView) mConfirmCancelOverlay.findViewById(R.id.warning_message); - tv.setVisibility(View.VISIBLE); - tv.setText(String.format(getString(R.string.per_app_theme_removal_warning), - mTitle.getText())); - } - - disableActionButtons(); - mClickableView.setSoundEffectsEnabled(false); - } - - public void showDeleteThemeOverlay() { - if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; - mConfirmCancelOverlay.setTitle(R.string.delete_theme_overlay_title); - mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() - .getColor(R.color.delete_overlay_background)); - mConfirmCancelOverlay.setOnOverlayDismissedListener(mDeleteConfirmationListener); - getChooserActivity().lockPager(); - ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); - mConfirmCancelOverlay.setVisibility(View.VISIBLE); - mConfirmCancelOverlay.setAlpha(0f); - anim.setListener(null); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(1f).start(); - - disableActionButtons(); - mClickableView.setSoundEffectsEnabled(false); - } - - public void showResetThemeOverlay() { - if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; - mConfirmCancelOverlay.setTitle(R.string.reset_theme_overlay_title); - mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() - .getColor(R.color.apply_overlay_background)); - mConfirmCancelOverlay.setOnOverlayDismissedListener(mResetConfirmationListener); - getChooserActivity().lockPager(); - ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); - mConfirmCancelOverlay.setVisibility(View.VISIBLE); - mConfirmCancelOverlay.setAlpha(0f); - anim.setListener(null); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(1f).start(); - - disableActionButtons(); - mClickableView.setSoundEffectsEnabled(false); - } - - public void hideConfirmCancelOverlay() { - hideConfirmCancelOverlay(false); - } - - /** - * Hides the apply theme layout overlay and can apply the selected theme - * when the animation is finished. - * @param applyThemeWhenFinished If true, the current theme will be applied. - */ - private void hideConfirmCancelOverlay(final boolean applyThemeWhenFinished) { - getChooserActivity().unlockPager(); - ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); - mConfirmCancelOverlay.setVisibility(View.VISIBLE); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(0f).start(); - anim.setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mConfirmCancelOverlay.setVisibility(View.GONE); - if (applyThemeWhenFinished) applyTheme(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - - enableActionButtons(); - mClickableView.setSoundEffectsEnabled(true); - } - - public boolean isShowingCustomizeResetLayout() { - return mCustomizeResetLayout.getVisibility() == View.VISIBLE; - } - - public void showCustomizeResetLayout() { - if (mCustomizeResetLayout.getVisibility() == View.VISIBLE) return; - if (!mThemeTagLayout.isCustomizedTagEnabled()) { - mResetButton.setEnabled(false); - } else { - mResetButton.setEnabled(true); - } - getChooserActivity().lockPager(); - ViewPropertyAnimator anim = mCustomizeResetLayout.animate(); - mCustomizeResetLayout.setVisibility(View.VISIBLE); - mCustomizeResetLayout.setAlpha(0f); - anim.setListener(null); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(1f).start(); - - disableActionButtons(); - mClickableView.setSoundEffectsEnabled(false); - } - - public void hideCustomizeResetLayout() { - hideCustomizeResetLayout(CustomizeResetAction.Dismiss); - } - - private void hideCustomizeResetLayout(final CustomizeResetAction action) { - getChooserActivity().unlockPager(); - ViewPropertyAnimator anim = mCustomizeResetLayout.animate(); - mCustomizeResetLayout.setVisibility(View.VISIBLE); - anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); - anim.alpha(0f).start(); - anim.setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - mCustomizeResetLayout.setVisibility(View.GONE); - switch (action) { - case Customize: - getChooserActivity().expand(); - break; - case Reset: - resetTheme(); - break; - } - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - - enableActionButtons(); - mClickableView.setSoundEffectsEnabled(true); - } - - public void showThemeTagLayout() { - mThemeTagLayout.setVisibility(View.VISIBLE); - mThemeTagLayout.animate().alpha(1f).setStartDelay(ANIMATE_START_DELAY).start(); - } - - public void hideThemeTagLayout() { - mThemeTagLayout.setAlpha(0f); - mThemeTagLayout.setVisibility(View.GONE); - } - - public void hideProcessingOverlay() { - mProcessingThemeLayout.animate().alpha(0).withEndAction(new Runnable() { - @Override - public void run() { - mProcessingThemeLayout.setVisibility(View.GONE); - } - }).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); - mCustomize.setVisibility(View.VISIBLE); - mCustomize.setAlpha(0f); - mCustomize.animate().alpha(1f).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); - if (mDelete.getVisibility() != View.GONE) { - mDelete.setVisibility(View.VISIBLE); - mDelete.setAlpha(0f); - mDelete.animate().alpha(1f).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); - } - - enableActionButtons(); - mClickableView.setSoundEffectsEnabled(true); - } - - public void fadeInCards() { - for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { - final int key = mCardIdsToComponentTypes.keyAt(i); - if (key != mActiveCardId) { - ComponentCardView card = (ComponentCardView) getView().findViewById(key); - if (card != null) card.animateCardFadeIn(); - } - } - mActiveCardId = -1; - } - - public boolean componentsChanged() { - // If an external wallpaper/ls are set then something changed! - if (mExternalWallpaperUri != null || mExternalLockscreenUri != null) return true; - - for (String key : mSelectedComponentsMap.keySet()) { - if (!mPkgName.equals(mSelectedComponentsMap.get(key))) { - return true; - } - if (ThemesColumns.MODIFIES_LAUNCHER.equals(key) && - mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { - return true; - } - } - return false; - } - - protected boolean isThemeCustomized() { - final String themePkgName = getThemePackageName(); - for (String key : mSelectedComponentsMap.keySet()) { - final String selectedPkgName = mSelectedComponentsMap.get(key); - if (!themePkgName.equals(selectedPkgName)) { - return true; - } - if (mBaseThemeSupportedComponents.size() > 0 && - !mBaseThemeSupportedComponents.contains(key)) { - return true; - } - } - // finally check if we're missing anything from mBaseThemeSupportedComponents - for (String component : mBaseThemeSupportedComponents) { - if (!mSelectedComponentsMap.containsKey(component)) return true; - } - return false; - } - - public void clearChanges() { - mSelectedComponentsMap.clear(); - mExternalWallpaperUri = null; - mExternalLockscreenUri = null; - View none = mLockScreenCard.findViewById(R.id.none); - if (none != null && none.getVisibility() == View.VISIBLE) { - none.setVisibility(View.GONE); - } - TextView tv = (TextView) mLockScreenCard.findViewById(R.id.label); - if (tv != null) { - tv.setAlpha(1f); - tv.setBackgroundResource(R.drawable.wallpaper_label_bg); - } - getLoaderManager().restartLoader(LOADER_ID_ALL, null, ThemeFragment.this); - } - - public String getThemePackageName() { - if (mPkgName == null) { - // check if the package name is defined in the arguments bundle - Bundle bundle = getArguments(); - if (bundle != null) { - mPkgName = bundle.getString(ARG_PACKAGE_NAME); - } - } - return mPkgName; - } - - private void uninstallTheme() { - getChooserActivity().uninstallTheme(mPkgName); - } - - public void setCurrentTheme(Map currentTheme, - MutableLong currentWallpaperComponentId) { - mCurrentTheme = currentTheme; - mCurrentWallpaperComponentId = currentWallpaperComponentId; - } - - /** - * Slides the scrollview content up and adds a space view at the bottom - * of mAdditionalCards so all content can be visible above the selector. - * - * We are using a ValueAnimator here to scroll the content rather than calling - * mScrollView.smoothScrollBy() since the speed of that animation cannot be customized. - * @param yDelta - * @param selectorHeight - */ - public void slideContentIntoView(final int yDelta, int selectorHeight) { - Space space = (Space) mAdditionalCards.findViewById(ADDITIONAL_CONTENT_SPACE_ID); - if (space == null) { - // No space view yet so lets create it one - space = new Space(getActivity()); - space.setId(ADDITIONAL_CONTENT_SPACE_ID); - mAdditionalCards.addView(space, - new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, - selectorHeight)); - } else { - // Space view already exists so just update the LayoutParams - LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) space.getLayoutParams(); - params.height = selectorHeight; - space.setLayoutParams(params); - } - final int startY = mScrollView.getScrollY(); - final ValueAnimator scrollAnimator = - ValueAnimator.ofInt(startY, startY + yDelta); - scrollAnimator.setDuration(SLIDE_CONTENT_ANIM_DURATION); - scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - mScrollView.scrollTo(0, value); - } - }); - scrollAnimator.start(); - } - - public Map getSelectedComponentsMap() { - return mSelectedComponentsMap; - } - - /** - * Slides the scrollview content down and removes a space view at the bottom - * of mAdditionalCards. - * - * We are using a ValueAnimator here to scroll the content rather than calling - * mScrollView.smoothScrollBy() since the speed of that animation cannot be customized. - * @param yDelta - */ - public void slideContentBack(int yDelta) { - final int startY = mScrollView.getScrollY(); - final ValueAnimator scrollAnimator = - ValueAnimator.ofInt(startY, startY + yDelta); - scrollAnimator.setDuration(SLIDE_CONTENT_ANIM_DURATION); - scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - mScrollView.scrollTo(0, value); - } - }); - scrollAnimator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - View space = mAdditionalCards.findViewById(ADDITIONAL_CONTENT_SPACE_ID); - if (space != null) mAdditionalCards.removeView(space); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - scrollAnimator.start(); - } - - public void showLockScreenCard() { - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - final int scrollToY = mStatusBarCard.getMeasuredHeight() - + mFontCard.getMeasuredHeight() + mIconCard.getMeasuredHeight() - + mNavBarCard.getMeasuredHeight() + mWallpaperCard.getMeasuredHeight() / 2; - final ValueAnimator scrollAnimator = ValueAnimator.ofInt(0, scrollToY); - scrollAnimator.setDuration(LOCK_SCREEN_CARD_SCROLL_ANIMATION_DURATION); - scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - int value = (Integer) animation.getAnimatedValue(); - mScrollView.scrollTo(0, value); - } - }); - scrollAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - mCardClickListener.onClick(mLockScreenCard); - } - }); - scrollAnimator.start(); - } - }, SHOW_LOCK_SCREEN_CARD_DELAY); - } - - protected void startLiveLockScreenSettings() { - Intent intent = new Intent(cyanogenmod.content.Intent.ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS); - try { - startActivity(intent); - } catch (ActivityNotFoundException e) { - // TODO: inform user that this action failed (Toast?) - } - } - - class AnimationLoader extends AsyncTask { - Context mContext; - String mPkgName; - BootAniImageView mBootAnim; - - public AnimationLoader(Context context, String pkgName, BootAniImageView bootAnim) { - mContext = context; - mPkgName = pkgName; - mBootAnim = bootAnim; - } - - @Override - protected void onPreExecute() { - super.onPreExecute(); - } - - @Override - protected Boolean doInBackground(Void... params) { - if (mContext == null) { - return Boolean.FALSE; - } - ZipFile zip = null; - if (ThemeConfig.SYSTEM_DEFAULT.equals(mPkgName)) { - try { - zip = new ZipFile(new File(BootAnimationHelper.SYSTEM_BOOT_ANI_PATH)); - } catch (Exception e) { - Log.w(TAG, "Unable to load boot animation", e); - return Boolean.FALSE; - } - } else { - // check if the bootanimation is cached - File f = new File(mContext.getCacheDir(), - mPkgName + BootAnimationHelper.CACHED_SUFFIX); - if (!f.exists()) { - // go easy on cache storage and clear out any previous boot animations - BootAnimationHelper.clearBootAnimationCache(mContext); - try { - Context themeContext = mContext.createPackageContext(mPkgName, 0); - AssetManager am = themeContext.getAssets(); - InputStream is = am.open("bootanimation/bootanimation.zip"); - FileUtils.copyToFile(is, f); - is.close(); - } catch (Exception e) { - Log.w(TAG, "Unable to load boot animation", e); - return Boolean.FALSE; - } - } - try { - zip = new ZipFile(f); - } catch (IOException e) { - Log.w(TAG, "Unable to load boot animation", e); - return Boolean.FALSE; - } - } - if (zip != null) { - mBootAnim.setBootAnimation(zip); - } else { - return Boolean.FALSE; - } - return Boolean.TRUE; - } - - @Override - protected void onPostExecute(Boolean isSuccess) { - super.onPostExecute(isSuccess); - if (isSuccess) { - mBootAnim.start(); - } - } - } - - class AudibleLoadingThread extends Thread { - private Context mContext; - private int mType; - private String mPkgName; - private MediaPlayer mPlayer; - - public AudibleLoadingThread(Context context, int type, String pkgName, MediaPlayer mp) { - super(); - mContext = context; - mType = type; - mPkgName = pkgName; - mPlayer = mp; - } - - @Override - public void run() { - try { - AudioUtils.loadThemeAudible(mContext, mType, mPkgName, mPlayer); - } catch (PackageManager.NameNotFoundException e) { - Log.w(TAG, "Unable to load sound for " + mPkgName, e); - } - } - } -} diff --git a/src/com/cyngn/theme/chooser/WallpaperCardView.java b/src/com/cyngn/theme/chooser/WallpaperCardView.java deleted file mode 100644 index 337af65..0000000 --- a/src/com/cyngn/theme/chooser/WallpaperCardView.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.chooser; - -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewOverlay; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -public class WallpaperCardView extends ComponentCardView { - protected ImageView mImage; - protected TextView mLabel; - - public WallpaperCardView(Context context) { - this(context, null); - } - - public WallpaperCardView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public WallpaperCardView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WallpaperCardView); - String labelText = a.getString(R.styleable.WallpaperCardView_labelText); - a.recycle(); - - setOrientation(VERTICAL); - - setBackgroundResource(R.drawable.card_bg); - - LayoutInflater inflater = LayoutInflater.from(mContext); - FrameLayout frameLayout = - (FrameLayout) inflater.inflate(R.layout.wallpaper_card, this, false); - addView(frameLayout); - mLabel = (TextView) frameLayout.findViewById(R.id.label); - mImage = (ImageView) frameLayout.findViewById(R.id.image); - - mLabel.setText(labelText); - } - - public void setWallpaper(Drawable drawable) { - mImage.setImageDrawable(drawable); - View none = findViewById(R.id.none); - if (drawable == null) { - setBackgroundResource(R.drawable.card_wallpapertoggled_bg); - if (none != null) { - none.setVisibility(View.VISIBLE); - } - if (mLabel != null) { - mLabel.setBackgroundResource(0); - } - } else { - setBackgroundResource(0); - - if (none != null) { - none.setVisibility(View.GONE); - } - if (mLabel != null) { - mLabel.setBackgroundResource(R.drawable.wallpaper_label_bg); - } - } - } - - public void clearWallpaper() { - mImage.setImageDrawable(null); - setBackgroundResource(R.drawable.card_bg); - } - - public Drawable getWallpaperDrawable() { - return mImage.getDrawable(); - } - - @Override - public void expand(boolean showLabel) { - setEnabled(true); - } - - @Override - public void collapse() { - setEnabled(false); - } - - /** - * Animates a change in the content of the card - * @param v View in card to animate - * @param overlay Drawable to animate as a ViewOverlay - * @param duration Duration of animation - */ - @Override - public void animateContentChange(View v, final Drawable overlay, long duration) { - // Since the wallpaper IS the content, we will ignore the view passed in and animate - // the entire card - final ViewOverlay viewOverlay = this.getOverlay(); - viewOverlay.add(overlay); - final int x = 0; - final int y = 0; - final int width = v.getWidth(); - final int height = v.getHeight(); - overlay.setBounds(x, y, x + width, y + height); - - final ValueAnimator overlayAnimator = ValueAnimator.ofFloat(1f, 0f); - overlayAnimator.setDuration(duration); - overlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - float value = (Float) animation.getAnimatedValue(); - overlay.setAlpha((int) (255 * value)); - } - - }); - overlayAnimator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - // Clear out the ViewOverlay now that we are done animating - viewOverlay.clear(); - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }); - - AnimatorSet set = new AnimatorSet(); - set.play(ObjectAnimator.ofFloat(overlay, "alpha", 0f, 1f)) - .with(overlayAnimator); - set.start(); - } -} diff --git a/src/com/cyngn/theme/perapptheming/PerAppThemeListLayout.java b/src/com/cyngn/theme/perapptheming/PerAppThemeListLayout.java deleted file mode 100644 index e475351..0000000 --- a/src/com/cyngn/theme/perapptheming/PerAppThemeListLayout.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (C) 2015 Cyanogen, Inc. - */ -package com.cyngn.theme.perapptheming; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Path; -import android.graphics.PointF; -import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.view.animation.Animation; -import android.widget.FrameLayout; - -import com.cyngn.theme.chooser.R; - -public class PerAppThemeListLayout extends FrameLayout { - private PerAppThemingWindow mWindow; - private PointF mCenter; - - private float mMaxRadius; - private float mTargetRadius; - private float mStartRadius; - private float mCurrentRadius; - - private ValueAnimator mAnimator; - private boolean mIsAnimating; - - private Path mRevealPath; - - public PerAppThemeListLayout(Context context) { - this(context, null); - } - - public PerAppThemeListLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PerAppThemeListLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - final Resources res = getResources(); - float width = res.getDimension(R.dimen.theme_list_width); - float height = res.getDimension(R.dimen.theme_list_max_height); - mMaxRadius = (float) Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); - mRevealPath = new Path(); - - mAnimator = new ValueAnimator(); - mAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); - mAnimator.addListener(mAnimationListener); - mAnimator.addUpdateListener(mUpdateListener); - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN && - event.getKeyCode() == KeyEvent.KEYCODE_BACK && mWindow != null) { - mWindow.hideThemeList(); - } - return super.dispatchKeyEvent(event); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (isEnabled() && event.getAction() == MotionEvent.ACTION_DOWN && mWindow != null) { - mWindow.hideThemeList(); - return true; - } - return super.onTouchEvent(event); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - if (!mIsAnimating) { - super.dispatchDraw(canvas); - } else { - final int state = canvas.save(); - mRevealPath.reset(); - mRevealPath.addCircle(mCenter.x, mCenter.y, mCurrentRadius, Path.Direction.CW); - canvas.clipPath(mRevealPath); - super.dispatchDraw(canvas); - canvas.restoreToCount(state); - } - } - - public void setPerAppThemingWindow(PerAppThemingWindow window) { - mWindow = window; - } - - /** - * Perform a circular reveal from center cx,cy - * @param cx X position of center - * @param cy Y position of center - * @param duration Duration of animation - */ - public void circularReveal(float cx, float cy, long duration) { - mCenter = new PointF(cx, cy); - mIsAnimating = true; - - mStartRadius = mCurrentRadius; - mTargetRadius = mMaxRadius; - startAnimation(duration); - } - - /** - * Perform a circular hide from center cx,cy - * @param cx X position of center - * @param cy Y position of center - * @param duration Duration of animation - */ - public void circularHide(float cx, float cy, long duration) { - mCenter = new PointF(cx, cy); - mIsAnimating = true; - - mStartRadius = mCurrentRadius; - mTargetRadius = 0f; - startAnimation(duration); - } - - private void startAnimation(long duration) { - getChildAt(0).setVisibility(View.VISIBLE); - mAnimator.setFloatValues(mStartRadius, mTargetRadius); - mAnimator.setDuration(duration); - mAnimator.start(); - } - - private ValueAnimator.AnimatorUpdateListener mUpdateListener = - new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - Float value = (Float) animation.getAnimatedValue(); - mCurrentRadius = value.floatValue(); - invalidate(); - } - }; - - private Animator.AnimatorListener mAnimationListener = new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) {} - - @Override - public void onAnimationEnd(Animator animation) { - mIsAnimating = false; - if (mCurrentRadius <= 0) { - getChildAt(0).setVisibility(INVISIBLE); - } - } - - @Override - public void onAnimationCancel(Animator animation) {} - - @Override - public void onAnimationRepeat(Animator animation) {} - }; -} diff --git a/src/com/cyngn/theme/perapptheming/PerAppThemeListView.java b/src/com/cyngn/theme/perapptheming/PerAppThemeListView.java deleted file mode 100644 index 5a4424f..0000000 --- a/src/com/cyngn/theme/perapptheming/PerAppThemeListView.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 Cyanogen, Inc. - */ -package com.cyngn.theme.perapptheming; - -import android.content.Context; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.widget.ListView; -import com.cyngn.theme.chooser.R; -import com.cyngn.theme.util.Utils; - -public class PerAppThemeListView extends ListView { - private int mMinHeight; - private int mMaxHeight; - - public PerAppThemeListView(Context context) { - this(context, null); - } - - public PerAppThemeListView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PerAppThemeListView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - final Resources res = getResources(); - TypedArray a = context.obtainStyledAttributes(attrs, - Utils.getResourceDeclareStyleableIntArray("com.android.internal", "View")); - int resId = res.getIdentifier("View_minHeight", "styleable", "android"); - mMinHeight = a.getDimensionPixelSize(resId, 0); - a.recycle(); - - a = context.obtainStyledAttributes(attrs, R.styleable.PerAppThemeListView); - mMaxHeight = a.getDimensionPixelSize(R.styleable.PerAppThemeListView_maxHeight, 0); - a.recycle(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - // let the super do the heavy lifting and then we'll cap the values to any max and/or min - // values that were defined in the layout - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - int measuredWidth = getMeasuredWidth(); - int measuredHeight = getMeasuredHeight(); - int newHeight = measuredHeight; - if (mMaxHeight > 0) { - newHeight = Math.min(measuredHeight, mMaxHeight); - } - if (mMinHeight > 0) { - newHeight = Math.max(newHeight, mMinHeight); - } - if (newHeight != measuredHeight) { - setMeasuredDimension(measuredWidth, newHeight); - } - } -} diff --git a/src/com/cyngn/theme/perapptheming/PerAppThemingWindow.java b/src/com/cyngn/theme/perapptheming/PerAppThemingWindow.java deleted file mode 100644 index 4b6857b..0000000 --- a/src/com/cyngn/theme/perapptheming/PerAppThemingWindow.java +++ /dev/null @@ -1,1064 +0,0 @@ -/* - * Copyright (C) 2015 Cyanogen, Inc. - */ -package com.cyngn.theme.perapptheming; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.database.ContentObserver; -import android.database.Cursor; -import android.graphics.PixelFormat; -import android.net.Uri; -import android.os.Handler; -import android.os.IBinder; -import android.text.TextUtils; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnTouchListener; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.WindowManager; -import android.view.animation.Interpolator; -import android.view.animation.OvershootInterpolator; -import android.widget.AdapterView; -import android.widget.BaseAdapter; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.ListView; -import android.widget.TextView; -import android.widget.Toast; - -import com.cyngn.theme.chooser.R; -import com.cyngn.theme.util.Utils; - -import cyanogenmod.providers.ThemesContract.ThemesColumns; -import cyanogenmod.themes.ThemeChangeRequest; -import cyanogenmod.themes.ThemeManager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -public class PerAppThemingWindow extends Service implements OnTouchListener, - ThemeManager.ThemeChangeListener { - // Animation frame rate per second - private static final int ANIMATION_FRAME_RATE = 60; - - private static final int EXIT_DELETE_MODE_ANIMATION_DURATION = 50; - - private static final int MOVE_TO_DELETE_BOX_ANIMATION_DURATION = 150; - - private static final int ANIMATION_DURATION = 300; - - private static final int FAB_SCALE_ANIMATION_DURATION = 150; - - private static final int LIST_ON_LEFT_SIDE = 0; - private static final int LIST_ON_RIGHT_SIDE = 1; - - // Don't want these colors to be themable and possibly alter the effect we are after, so - // they are defined here rather than in colors.xml - private static final int SCRIM_COLOR_TRANSPARENT = 0x00000000; - private static final int SCRIM_COLOR_OPAQUE = 0xaa000000; - - // Amount to wait after a theme change occurred before fading the scrim away - // This value was obtained empirically by performing theme changes and adjusting this delay - private static final int THEME_CHANGE_DELAY = 1500; - - private static final float PRESSED_FAB_SCALE = 0.95f; - - private static final float DELETE_BOX_ANIMATION_SCALE = 0.3f; - - private static final int MAX_DEPRECIATION = 5; - - private static final float FAB_ANIMATION_SCALE_FACTOR = 0.44f; - - // Margin around the phone - private static int MARGIN_VERTICAL; - // Margin around the phone - private static int MARGIN_HORIZONTAL; - private static int CLOSE_ANIMATION_DISTANCE; - private static int DRAG_DELTA; - private static int STARTING_POINT_Y; - private static int DELETE_BOX_WIDTH; - private static int DELETE_BOX_HEIGHT; - private static int FLOATING_WINDOW_ICON_SIZE; - - // View variables - private BroadcastReceiver mBroadcastReceiver; - private WindowManager mWindowManager; - private LinearLayout mDraggableIcon; - private View mDraggableIconImage; - private WindowManager.LayoutParams mParams; - private PerAppThemeListLayout mThemeListLayout; - private WindowManager.LayoutParams mListLayoutParams; - private ListView mThemeList; - private ThemesAdapter mAdapter; - private FrameLayout.LayoutParams mListParams; - private LinearLayout mDeleteView; - private View mDeleteBoxView; - private View mThemeApplyingView; - private boolean mDeleteBoxVisible = false; - private boolean mIsDestroyed = false; - private boolean mIsBeingDestroyed = false; - private int mCurrentPosX = -1; - - // Animation variables - private List mDeltaXArray; - private List mDeltaYArray; - private AnimationTask mAnimationTask; - - // Close logic - private int mCurrentX; - private int mCurrentY; - private boolean mIsInDeleteMode = false; - private boolean mIsAnimationLocked = false; - - // Drag variables - float mPrevDragX; - float mPrevDragY; - float mOrigX; - float mOrigY; - boolean mDragged; - - private int mListSide = LIST_ON_LEFT_SIDE; - - private ThemeConfig mThemeConfig; - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public void onCreate() { - super.onCreate(); - - // Load margins, distances, etc. - final Resources res = getResources(); - MARGIN_VERTICAL = - res.getDimensionPixelSize(R.dimen.floating_window_margin_vertical); - MARGIN_HORIZONTAL = - res.getDimensionPixelSize(R.dimen.floating_window_margin_horizontal); - CLOSE_ANIMATION_DISTANCE = - res.getDimensionPixelSize(R.dimen.floating_window_close_animation_distance); - DRAG_DELTA = res.getDimensionPixelSize(R.dimen.floating_window_drag_delta); - STARTING_POINT_Y = res.getDimensionPixelSize(R.dimen.floating_window_starting_point_y); - - DELETE_BOX_WIDTH = (int) getResources().getDimension( - R.dimen.floating_window_delete_box_width); - DELETE_BOX_HEIGHT = (int) getResources().getDimension( - R.dimen.floating_window_delete_box_height); - FLOATING_WINDOW_ICON_SIZE = (int) getResources().getDimension( - R.dimen.floating_window_icon); - - mDeleteView = new LinearLayout(getContext()); - View.inflate(getContext(), R.layout.per_app_delete_box_window, mDeleteView); - mDeleteBoxView = mDeleteView.findViewById(R.id.box); - addView(mDeleteView, 0, 0, Gravity.BOTTOM | Gravity.CENTER_VERTICAL, - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.WRAP_CONTENT); - mDeleteView.setVisibility(View.GONE); - - mDraggableIcon = new LinearLayout(this); - mDraggableIcon.setOnTouchListener(this); - View.inflate(getContext(), R.layout.per_app_fab_floating_window_icon, mDraggableIcon); - mDraggableIconImage = mDraggableIcon.findViewById(R.id.box); - mDraggableIconImage.setClipToOutline(true); - mDraggableIconImage.getViewTreeObserver().addOnWindowAttachListener(mWindowAttachListener); - mParams = addView(mDraggableIcon, 0, 0); - updateIconPosition(MARGIN_HORIZONTAL, STARTING_POINT_Y); - - mThemeListLayout = (PerAppThemeListLayout) View.inflate(getContext(), - R.layout.per_app_theme_list, null); - mThemeListLayout.setPerAppThemingWindow(this); - mThemeList = (ListView) mThemeListLayout.findViewById(R.id.theme_list); - mListParams = (FrameLayout.LayoutParams) mThemeList.getLayoutParams(); - mThemeApplyingView = mThemeListLayout.findViewById(R.id.applying_theme_text); - - final Configuration config = getResources().getConfiguration(); - mThemeConfig = getThemeConfig(config); - loadThemes(); - getContentResolver().registerContentObserver(ThemesColumns.CONTENT_URI, true, - mThemesObserver); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (mIsBeingDestroyed) return true; - - switch(event.getAction()) { - case MotionEvent.ACTION_DOWN: - if (mThemeListLayout.isAttachedToWindow()) { - hideThemeList(); - return false; - } - mPrevDragX = mOrigX = event.getRawX(); - mPrevDragY = mOrigY = event.getRawY(); - - mDragged = false; - - mDeltaXArray = new LinkedList(); - mDeltaYArray = new LinkedList(); - - mCurrentX = mParams.x; - mCurrentY = mParams.y; - - mDraggableIconImage.setScaleX(PRESSED_FAB_SCALE); - mDraggableIconImage.setScaleY(PRESSED_FAB_SCALE); - - // Cancel any currently running animations - if (mAnimationTask != null) { - mAnimationTask.cancel(); - } - break; - case MotionEvent.ACTION_UP: - mIsAnimationLocked = false; - if (mAnimationTask != null) { - mAnimationTask.cancel(); - } - - if (!mDragged) { - // clicked so show theme list - final int mid = getScreenWidth() / 2; - mListSide = LIST_ON_LEFT_SIDE; - if (mCurrentPosX > mid) mListSide = LIST_ON_RIGHT_SIDE; - if (!mThemeListLayout.isAttachedToWindow()) showThemeList(); - } else { - // Animate the icon - mAnimationTask = new AnimationTask(); - mAnimationTask.run(); - } - - if (mIsInDeleteMode) { - close(true); - } else { - hideDeleteBox(); - mDraggableIconImage.setScaleX(1f); - mDraggableIconImage.setScaleY(1f); - } - break; - case MotionEvent.ACTION_MOVE: - mCurrentX = (int) (event.getRawX() - mDraggableIcon.getWidth() / 2); - mCurrentY = (int) (event.getRawY() - mDraggableIcon.getHeight()); - if (isDeleteMode()) { - mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_hover); - mIsInDeleteMode = true; - updateIconPosition(mCurrentX, mCurrentY); - } else if (mIsInDeleteMode){ - mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_normal); - mIsInDeleteMode = false; - } else { - if(!mIsAnimationLocked && mDragged) { - if (mAnimationTask != null) { - mAnimationTask.cancel(); - } - updateIconPosition(mCurrentX, mCurrentY); - } - } - - float deltaX = event.getRawX() - mPrevDragX; - float deltaY = event.getRawY() - mPrevDragY; - - mDeltaXArray.add(deltaX); - mDeltaYArray.add(deltaY); - - mPrevDragX = event.getRawX(); - mPrevDragY = event.getRawY(); - - deltaX = event.getRawX() - mOrigX; - deltaY = event.getRawY() - mOrigY; - mDragged = mDragged || Math.abs(deltaX) > DRAG_DELTA - || Math.abs(deltaY) > DRAG_DELTA; - if (mDragged) { - showDeleteBox(); - } - break; - } - - return true; - } - - @Override - public void onDestroy() { - super.onDestroy(); - mIsDestroyed = true; - if (mDraggableIcon != null) { - removeViewIfAttached(mDraggableIcon); - mDraggableIcon = null; - } - if (mDeleteView != null) { - removeViewIfAttached(mDeleteView); - mDeleteView = null; - } - if (mThemeListLayout != null) { - removeViewIfAttached(mThemeListLayout); - mThemeListLayout = null; - } - if (mAnimationTask != null) { - mAnimationTask.cancel(); - mAnimationTask = null; - } - if (mBroadcastReceiver != null) { - unregisterReceiver(mBroadcastReceiver); - mBroadcastReceiver = null; - } - if (mThemesObserver != null) { - getContentResolver().unregisterContentObserver(mThemesObserver); - mThemesObserver = null; - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mThemeConfig = getThemeConfig(newConfig); - } - - @Override - public void onProgress(int progress) { - } - - @Override - public void onFinish(boolean isSuccess) { - ThemeManager tm = ThemeManager.getInstance(getContext()); - tm.removeClient(this); - mThemeListLayout.postDelayed(new Runnable() { - @Override - public void run() { - hideScrim(); - startFabScaleUpAnimation(); - } - }, THEME_CHANGE_DELAY); - } - - public void hideThemeList() { - hideThemeList(false, new Runnable() { - @Override - public void run() { - removeViewIfAttached(mThemeListLayout); - } - }); - } - - private ThemeConfig getThemeConfig(Configuration config) { - if (config != null && config.themeConfig != null) { - return config.themeConfig; - } - - return ThemeConfig.getBootTheme(getContentResolver()); - } - - private void removeViewIfAttached(View view) { - if (view.isAttachedToWindow()) { - mWindowManager.removeViewImmediate(view); - } - } - - private WindowManager.LayoutParams addView(View v, int x, int y) { - return addView(v, x, y, Gravity.TOP | Gravity.LEFT, - WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); - } - - private WindowManager.LayoutParams addView(View v, int x, int y, int gravity, - int width, int height) { - mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); - WindowManager.LayoutParams params = new WindowManager.LayoutParams(width, height, - WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); - - params.gravity = gravity; - params.x = x; - params.y = y; - - mWindowManager.addView(v, params); - - return params; - } - - private void updateIconPosition(int x, int y) { - mCurrentPosX = x; - - View v = mDraggableIconImage; - v.setTranslationX(0); - if (x < 0) { - v.setTranslationX(x); - x = 0; - } - - if (x > getScreenWidth() - FLOATING_WINDOW_ICON_SIZE) { - v.setTranslationX(x - getScreenWidth() + FLOATING_WINDOW_ICON_SIZE); - x = getScreenWidth() - FLOATING_WINDOW_ICON_SIZE; - } - - v.setTranslationY(0); - if (y < 0) { - v.setTranslationY(y); - y = 0; - } - - if (y > getScreenHeight() - FLOATING_WINDOW_ICON_SIZE) { - v.setTranslationY(y - getScreenHeight() + FLOATING_WINDOW_ICON_SIZE); - y = getScreenHeight() - FLOATING_WINDOW_ICON_SIZE; - } - mParams.x = x; - mParams.y = y; - - if (!mIsDestroyed) { - mWindowManager.updateViewLayout(mDraggableIcon, mParams); - } - } - - private boolean isDeleteMode() { - return isHoveringOverDeleteBox(mParams.y); - } - - private boolean isHoveringOverDeleteBox(int y) { - return y + mDraggableIconImage.getHeight() >= getScreenHeight() - DELETE_BOX_HEIGHT; - } - - private void showDeleteBox() { - if (!mDeleteBoxVisible) { - mDeleteBoxVisible = true; - mDeleteView.setVisibility(View.VISIBLE); - - mDeleteBoxView.setAlpha(0); - mDeleteBoxView.setTranslationY(CLOSE_ANIMATION_DISTANCE); - mDeleteBoxView.animate().alpha(1).translationYBy(-1 * CLOSE_ANIMATION_DISTANCE) - .setListener(null); - - mDeleteBoxView.getLayoutParams().width = getScreenWidth(); - } - } - - private void hideDeleteBox() { - if (mDeleteBoxVisible) { - mDeleteBoxVisible = false; - if (mDeleteView != null) { - mDeleteBoxView.animate().alpha(0) - .translationYBy(CLOSE_ANIMATION_DISTANCE) - .setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - if (mDeleteView != null) mDeleteView.setVisibility(View.GONE); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - } - } - } - - private void animateToDeleteBoxCenter(final OnAnimationFinishedListener l) { - if (mIsAnimationLocked) { - return; - } - mIsInDeleteMode = true; - - if (mAnimationTask != null) { - mAnimationTask.cancel(); - } - - mAnimationTask = new AnimationTask(getScreenWidth() / 2 - mDraggableIcon.getWidth() / 2, - getScreenHeight() - DELETE_BOX_HEIGHT / 2 - mDraggableIcon.getHeight() / 2); - mAnimationTask.setDuration(MOVE_TO_DELETE_BOX_ANIMATION_DURATION); - mAnimationTask.setAnimationFinishedListener(l); - mAnimationTask.run(); - mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_hover); - } - - private void close(boolean animate) { - if (mIsBeingDestroyed) { - return; - } - mIsBeingDestroyed = true; - - if (animate) { - animateToDeleteBoxCenter(new OnAnimationFinishedListener() { - @Override - public void onAnimationFinished() { - hideDeleteBox(); - mDeleteBoxView.animate() - .scaleX(DELETE_BOX_ANIMATION_SCALE) - .scaleY(DELETE_BOX_ANIMATION_SCALE); - mDraggableIconImage.animate() - .scaleX(DELETE_BOX_ANIMATION_SCALE) - .scaleY(DELETE_BOX_ANIMATION_SCALE) - .translationY(CLOSE_ANIMATION_DISTANCE) - .setDuration(mDeleteBoxView.animate().getDuration()) - .setListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - stopSelf(); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - } - }); - } else { - stopSelf(); - } - } - - private static interface OnAnimationFinishedListener { - public void onAnimationFinished(); - } - - private Context getContext() { - return this; - } - - private int getScreenWidth() { - return getResources().getDisplayMetrics().widthPixels; - } - - private int getScreenHeight() { - return getResources().getDisplayMetrics().heightPixels - getStatusBarHeight(); - } - - private int getStatusBarHeight() { - int result = 0; - int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); - if (resourceId > 0) { - result = getResources().getDimensionPixelSize(resourceId); - } - - return result; - } - - private void loadThemes() { - String[] columns = {ThemesColumns._ID, ThemesColumns.TITLE, ThemesColumns.PKG_NAME}; - String selection = ThemesColumns.MODIFIES_OVERLAYS + "=? AND " + - ThemesColumns.INSTALL_STATE + "=?"; - String[] selectionArgs = {"1", "" + ThemesColumns.InstallState.INSTALLED}; - String sortOrder = ThemesColumns.TITLE + " ASC"; - Cursor c = getContentResolver().query(ThemesColumns.CONTENT_URI, columns, selection, - selectionArgs, sortOrder); - if (c != null) { - if (mAdapter == null) { - mAdapter = new ThemesAdapter(this, c); - mThemeList.setAdapter(mAdapter); - mThemeList.setOnItemClickListener(mThemeClickedListener); - } else { - String pkgName = (String) mAdapter.getItem(0); - mAdapter.populateThemes(c); - mAdapter.setCurrentTheme(pkgName); - } - } - } - - private ContentObserver mThemesObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - onChange(selfChange, null); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - loadThemes(); - } - }; - - private ViewTreeObserver.OnWindowAttachListener mWindowAttachListener = - new ViewTreeObserver.OnWindowAttachListener() { - @Override - public void onWindowAttached() { - // Remove the this OnWindowAttachListener now that we are done with it. - mDraggableIconImage.getViewTreeObserver().removeOnWindowAttachListener(this); - - final float fabWidth = getResources().getDimension(R.dimen.floating_window_icon); - mDraggableIconImage.setAlpha(0); - mDraggableIconImage.setX(-fabWidth); - mDraggableIconImage.animate() - .alpha(1f) - .xBy(fabWidth) - .setDuration(ANIMATION_DURATION) - .start(); - } - - @Override - public void onWindowDetached() { - } - }; - - private void showThemeList() { - if (mListLayoutParams == null) { - mListLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_PHONE, - WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | - WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | - WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | - WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, - PixelFormat.TRANSLUCENT); - } - mListLayoutParams.gravity = Gravity.TOP | - (mListSide == LIST_ON_LEFT_SIDE ? Gravity.LEFT : Gravity.RIGHT); - mWindowManager.addView(mThemeListLayout, mListLayoutParams); - - setThemeListPosition(); - startFabScaleDownAnimation(); - - mAdapter.setCurrentTheme( - mThemeConfig.getOverlayPkgNameForApp(Utils.getTopTaskPackageName(this))); - mThemeListLayout.circularReveal(mParams.x + mDraggableIconImage.getWidth() / 2, - mParams.y + mDraggableIconImage.getHeight() / 2, ANIMATION_DURATION); - } - - private void hideThemeList(boolean showScrim, final Runnable endAction) { - if (showScrim) { - showScrim(); - } else { - startFabScaleUpAnimation(); - } - mThemeListLayout.circularHide(mParams.x + mDraggableIconImage.getWidth() / 2, - mParams.y + mDraggableIconImage.getHeight() / 2, ANIMATION_DURATION); - if (endAction != null) { - mDraggableIcon.postDelayed(endAction, ANIMATION_DURATION); - } - } - - private void showScrim() { - ValueAnimator animator = ValueAnimator.ofArgb(SCRIM_COLOR_TRANSPARENT, - SCRIM_COLOR_OPAQUE); - mThemeListLayout.setEnabled(false); - animator.setDuration(ANIMATION_DURATION) - .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - Integer value = (Integer) animation.getAnimatedValue(); - mThemeListLayout.setBackgroundColor(value.intValue()); - } - }); - animator.start(); - mThemeApplyingView.animate() - .alpha(1f) - .setDuration(ANIMATION_DURATION); - } - - private void hideScrim() { - ValueAnimator animator = ValueAnimator.ofArgb(SCRIM_COLOR_OPAQUE, SCRIM_COLOR_TRANSPARENT); - animator.setDuration(ANIMATION_DURATION) - .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - Integer value = (Integer) animation.getAnimatedValue(); - mThemeListLayout.setBackgroundColor(value.intValue()); - } - }); - animator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - removeViewIfAttached(mThemeListLayout); - } - - @Override - public void onAnimationCancel(Animator animation) { - } - - @Override - public void onAnimationRepeat(Animator animation) { - } - }); - animator.start(); - mThemeApplyingView.animate() - .alpha(0f) - .setDuration(ANIMATION_DURATION); - mDraggableIcon.setVisibility(View.VISIBLE); - mDraggableIconImage.animate() - .alpha(1f) - .setDuration(ANIMATION_DURATION); - } - - private void setThemeListPosition() { - int thirdHeight = getScreenHeight() / 3; - // use the center of the fab to decide where to place the list - int fabLocationY = mParams.y + mDraggableIconImage.getHeight() / 2; - int listHeight = mThemeList.getMeasuredHeight(); - if (listHeight <= 0) { - // not measured yet so let's force that - int width = getResources().getDimensionPixelSize(R.dimen.theme_list_width); - int height = getResources().getDimensionPixelSize(R.dimen.theme_list_max_height); - mThemeList.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST)); - listHeight = mThemeList.getMeasuredHeight(); - } - - // If we're in the top 1/3 of the screen position the top of the list with the top - // of the fab. Second 3rd will position the list so that it is vertically centered - // with the fab center. Bottom 3rd will position the bottom of the list with the - // bottom of the fab. - if (fabLocationY < thirdHeight) { - mListParams.topMargin = mParams.y + mDraggableIconImage.getHeight() / 2; - } else if (fabLocationY < thirdHeight * 2) { - mListParams.topMargin = fabLocationY - listHeight / 2; - } else { - mListParams.topMargin = mParams.y + mDraggableIconImage.getHeight() / 2 - listHeight; - } - mListParams.gravity = Gravity.TOP | - (mListSide == LIST_ON_LEFT_SIDE ? Gravity.LEFT : Gravity.RIGHT); - mThemeList.setLayoutParams(mListParams); - } - - private void startFabScaleDownAnimation() { - final int iconWidth = mDraggableIconImage.getWidth(); - final float translateX = (iconWidth - (float) iconWidth * FAB_ANIMATION_SCALE_FACTOR) / 2 * - (mListSide == LIST_ON_LEFT_SIDE ? -1 : 1); - - mDraggableIconImage.animate() - .scaleX(FAB_ANIMATION_SCALE_FACTOR) - .scaleY(FAB_ANIMATION_SCALE_FACTOR) - .translationXBy(translateX) - .setDuration(FAB_SCALE_ANIMATION_DURATION); - } - - private void startFabScaleUpAnimation() { - final float iconWidth = mDraggableIconImage.getWidth(); - final float translateX = (iconWidth - (float) iconWidth * FAB_ANIMATION_SCALE_FACTOR) / 2 * - (mListSide == LIST_ON_LEFT_SIDE ? 1 : -1); - - mDraggableIconImage.animate() - .scaleX(1f) - .scaleY(1f) - .translationXBy(translateX) - .setDuration(FAB_SCALE_ANIMATION_DURATION); - } - - private AdapterView.OnItemClickListener mThemeClickedListener = - new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView adapterView, View view, int i, long l) { - final String themePkgName = (String) view.getTag(R.id.tag_key_name); - final String appPkgName = Utils.getTopTaskPackageName(getContext()); - if (!TextUtils.isEmpty(appPkgName) && !TextUtils.isEmpty(themePkgName)) { - if (!Utils.themeHasOverlayForApp(getContext(), appPkgName, themePkgName)) { - Toast.makeText(getContext(), R.string.per_app_theme_app_not_overlaid_warning, - Toast.LENGTH_LONG).show(); - } - hideThemeList(true, new Runnable() { - @Override - public void run() { - ThemeManager tm = ThemeManager.getInstance(getContext()); - ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); - builder.setAppOverlay(appPkgName, themePkgName); - try { - tm.addClient(PerAppThemingWindow.this); - } catch (IllegalArgumentException e) { - /* ignore since this means we already have a listener added */ - } - tm.requestThemeChange(builder.build(), false); - } - }); - } else { - hideThemeList(); - } - } - }; - - private float calculateVelocityX() { - int depreciation = mDeltaXArray.size() + 1; - float sum = 0; - for (Float f : mDeltaXArray) { - depreciation--; - if (depreciation > MAX_DEPRECIATION){ - continue; - } - - sum += f / depreciation; - } - - return sum; - } - - private float calculateVelocityY() { - int depreciation = mDeltaYArray.size() + 1; - float sum = 0; - for (Float f : mDeltaYArray) { - depreciation--; - if (depreciation > 5) { - continue; - } - - sum += f / depreciation; - } - - return sum; - } - - // Timer for animation/automatic movement of the tray - private class AnimationTask { - // Ultimate destination coordinates toward which the view will move - int mDestX; - int mDestY; - long mDuration = 350; - long mStartTime; - float mTension = 1.4f; - Interpolator mInterpolator = new OvershootInterpolator(mTension); - long mSteps; - long mCurrentStep; - int mDistX; - int mOrigX; - int mDistY; - int mOrigY; - Handler mAnimationHandler = new Handler(); - OnAnimationFinishedListener mAnimationFinishedListener; - - public AnimationTask(int x, int y) { - setup(x, y); - } - - public AnimationTask() { - setup(calculateX(), calculateY()); - - float velocityX = calculateVelocityX(); - float velocityY = calculateVelocityY(); - mTension += Math.sqrt(velocityX * velocityX + velocityY * velocityY) / 200; - mInterpolator = new OvershootInterpolator(mTension); - } - - private void setup(int x, int y) { - if (mIsAnimationLocked) { - throw new RuntimeException("Returning to user's finger. Avoid animations while " + - "mIsAnimationLocked flag is set."); - } - - mDestX = x; - mDestY = y; - - mSteps = (int) (((float) mDuration) / 1000 * ANIMATION_FRAME_RATE); - mCurrentStep = 1; - mDistX = mParams.x - mDestX; - mOrigX = mParams.x; - mDistY = mParams.y - mDestY; - mOrigY = mParams.y; - } - - public long getDuration() { - return mDuration; - } - - public void setDuration(long duration) { - mDuration = duration; - setup(mDestX, mDestY); - } - - public OnAnimationFinishedListener getAnimationFinishedListener() { - return mAnimationFinishedListener; - } - - public void setAnimationFinishedListener(OnAnimationFinishedListener l) { - mAnimationFinishedListener = l; - } - - public Interpolator getInterpolator() { - return mInterpolator; - } - - public void setInterpolator(Interpolator interpolator) { - mInterpolator = interpolator; - } - - private int calculateX() { - float velocityX = calculateVelocityX(); - int screenWidth = getScreenWidth(); - int destX = (mParams.x + mDraggableIcon.getWidth() / 2 > screenWidth / 2) - ? screenWidth - mDraggableIcon.getWidth() - MARGIN_HORIZONTAL - : 0 + MARGIN_HORIZONTAL; - - if (Math.abs(velocityX) > 50) { - destX = (velocityX > 0) ? screenWidth - mDraggableIcon.getWidth() - - MARGIN_HORIZONTAL : 0 + MARGIN_HORIZONTAL; - } - - return destX; - } - - private int calculateY() { - float velocityY = calculateVelocityY(); - mInterpolator = new OvershootInterpolator(mTension); - int screenHeight = getScreenHeight(); - int destY = mParams.y + (int) (velocityY * 3); - if (destY <= 0) { - destY = MARGIN_VERTICAL; - } - if (destY >= screenHeight - mDraggableIcon.getHeight()) { - destY = screenHeight - mDraggableIcon.getHeight() - MARGIN_VERTICAL; - } - - return destY; - } - - public void run() { - mStartTime = System.currentTimeMillis(); - for (mCurrentStep = 1; mCurrentStep <= mSteps; mCurrentStep++) { - long delay = mCurrentStep * mDuration / mSteps; - final float currentStep = mCurrentStep; - mAnimationHandler.postDelayed(new Runnable() { - @Override - public void run() { - // Update coordinates of the view - float percent = mInterpolator.getInterpolation(currentStep / mSteps); - updateIconPosition(mOrigX - (int) (percent * mDistX), mOrigY - - (int) (percent * mDistY)); - - // Notify the animation has ended - if (currentStep >= mSteps) { - if (mAnimationFinishedListener != null) mAnimationFinishedListener - .onAnimationFinished(); - } - } - }, delay); - } - } - - public void cancel() { - mAnimationHandler.removeCallbacksAndMessages(null); - mAnimationTask = null; - } - } - - /** - * We're extending BaseAdapter rather than CursorAdapter so that we can quickly re-order - * the list without needing to requery the provider. We're only storing the package name - * and theme title so there is minimum memory impact on doing this. - */ - class ThemesAdapter extends BaseAdapter { - private static final float HALF_OPACITY = 0.5f; - private static final float FULL_OPACITY = 1.0f; - - private ArrayList mThemes; - private LayoutInflater mInflater; - - public ThemesAdapter(Context context, Cursor cursor) { - mInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE); - mThemes = new ArrayList(cursor.getCount()); - populateThemes(cursor); - cursor.close(); - } - - @Override - public int getCount() { - return mThemes.size(); - } - - @Override - public Object getItem(int position) { - return mThemes.get(position).pkgName; - } - - @Override - public long getItemId(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - if (convertView == null) { - convertView = mInflater.inflate(R.layout.per_app_theme_list_item, parent, false); - Holder holder = new Holder(); - holder.title = (TextView) convertView.findViewById(R.id.theme_title); - holder.indicator = (TextView) convertView.findViewById(R.id.selected_indicator); - convertView.setTag(R.id.tag_key_holder, holder); - } - ThemeInfo themeInfo = mThemes.get(position); - Holder holder = (Holder) convertView.getTag(R.id.tag_key_holder); - holder.title.setText(themeInfo.title); - if (position == 0) { - holder.title.setAlpha(HALF_OPACITY); - holder.indicator.setVisibility(View.VISIBLE); - convertView.setEnabled(false); - } else { - holder.title.setAlpha(FULL_OPACITY); - holder.indicator.setVisibility(View.INVISIBLE); - convertView.setEnabled(true); - } - convertView.setTag(R.id.tag_key_name, themeInfo.pkgName); - return convertView; - } - - @Override - public boolean isEnabled(int position) { - return position != 0; - } - - public void setCurrentTheme(String pkgName) { - ThemeInfo info = null; - for (ThemeInfo ti : mThemes) { - if (ti.pkgName.equals(pkgName)) { - info = ti; - break; - } - } - if (info != null) { - Collections.sort(mThemes); - mThemes.remove(info); - mThemes.add(0, info); - notifyDataSetChanged(); - } - } - - private void populateThemes(Cursor cursor) { - mThemes.clear(); - while(cursor.moveToNext()) { - ThemeInfo info = new ThemeInfo( - cursor.getString(cursor.getColumnIndex(ThemesColumns.PKG_NAME)), - cursor.getString(cursor.getColumnIndex(ThemesColumns.TITLE))); - mThemes.add(info); - } - } - - private class Holder { - TextView title; - TextView indicator; - } - - private class ThemeInfo implements Comparable { - String pkgName; - String title; - - public ThemeInfo(String pkgName, String title) { - this.pkgName = pkgName; - this.title = title; - } - - @Override - public int compareTo(Object another) { - return this.title.compareTo(((ThemeInfo)another).title); - } - } - } -} diff --git a/src/com/cyngn/theme/util/AudioUtils.java b/src/com/cyngn/theme/util/AudioUtils.java deleted file mode 100644 index 62076db..0000000 --- a/src/com/cyngn/theme/util/AudioUtils.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.util; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.AssetFileDescriptor; -import android.content.res.AssetManager; -import android.content.res.ThemeConfig; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.net.Uri; -import android.util.Log; - -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.File; -import java.io.IOException; - -public class AudioUtils { - private static final String TAG = AudioUtils.class.getSimpleName(); - - public static void loadThemeAudible(Context context, int type, String pkgName, MediaPlayer mp) - throws PackageManager.NameNotFoundException { - if (ThemeConfig.SYSTEM_DEFAULT.equals(pkgName)) { - loadSystemAudible(type, mp); - return; - } - PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); - Context themeCtx = context.createPackageContext(pkgName, 0); - AssetManager assetManager = themeCtx.getAssets(); - String assetPath; - switch (type) { - case RingtoneManager.TYPE_ALARM: - assetPath = "alarms"; - break; - case RingtoneManager.TYPE_NOTIFICATION: - assetPath = "notifications"; - break; - case RingtoneManager.TYPE_RINGTONE: - assetPath = "ringtones"; - break; - default: - assetPath = null; - break; - } - if (assetPath != null) { - try { - String[] assetList = assetManager.list(assetPath); - if (assetList != null && assetList.length > 0) { - AssetFileDescriptor afd = assetManager.openFd(assetPath - + File.separator + assetList[0]); - if (mp != null) { - mp.reset(); - mp.setDataSource(afd.getFileDescriptor(), - afd.getStartOffset(), afd.getLength()); - mp.prepare(); - } - } - } catch (IOException e) { - Log.e(TAG, "Unable to load sound for " + pkgName, e); - } - } - } - - public static void loadSystemAudible(int type, MediaPlayer mp) { - final String audiblePath = ThemeUtils.getDefaultAudiblePath(type); - if (audiblePath != null && (new File(audiblePath)).exists()) { - try { - mp.reset(); - mp.setDataSource(audiblePath); - mp.prepare(); - } catch (IOException e) { - Log.e(TAG, "Unable to load system sound " + audiblePath, e); - } - } - } - - public static Uri loadDefaultAudible(Context context, int type, MediaPlayer mp) - throws IOException { - Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); - if (ringtoneUri != null) { - mp.reset(); - mp.setDataSource(context, ringtoneUri); - mp.prepare(); - } - - return ringtoneUri; - } -} diff --git a/src/com/cyngn/theme/util/BootAnimationHelper.java b/src/com/cyngn/theme/util/BootAnimationHelper.java deleted file mode 100644 index 283f854..0000000 --- a/src/com/cyngn/theme/util/BootAnimationHelper.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.ActivityManager; -import android.content.Context; -import android.content.res.ThemeConfig; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.AsyncTask; -import android.text.TextUtils; -import android.util.Log; -import android.view.View; -import android.widget.ImageView; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; - -public class BootAnimationHelper { - private static final String TAG = BootAnimationHelper.class.getSimpleName(); - private static final int MAX_REPEAT_COUNT = 3; - - public static final String THEME_INTERNAL_BOOT_ANI_PATH = - "assets/bootanimation/bootanimation.zip"; - public static final String SYSTEM_BOOT_ANI_PATH = "/system/media/bootanimation.zip"; - public static final String CACHED_SUFFIX = "_bootanimation.zip"; - - public static final int NUM_FIRST_LINE_PARAMETERS = 3; - public static final int NUM_PART_LINE_PARAMETERS = 4; - - public static class AnimationPart { - /** - * Number of times to play this part - */ - public int playCount; - /** - * If non-zero, pause for the given # of seconds before moving on to next part. - */ - public int pause; - /** - * The name of this part - */ - public String partName; - /** - * Time each frame is displayed - */ - public int frameRateMillis; - /** - * List of file names for the given frames in this part - */ - public List frames; - /** - * width of the animation - */ - public int width; - /** - * height of the animation - */ - public int height; - - public AnimationPart(int playCount, int pause, String partName, int frameRateMillis, - int width, int height) { - this.playCount = playCount == 0 ? MAX_REPEAT_COUNT : playCount; - this.pause = pause; - this.partName = partName; - this.frameRateMillis = frameRateMillis; - this.width = width; - this.height = height; - frames = new ArrayList(); - } - - public void addFrame(String frame) { - frames.add(frame); - } - } - - /** - * Gather up all the details for the given boot animation - * @param zip The bootanimation.zip - * @return A list of AnimationPart if successful, null if not. - * @throws IOException - */ - public static List parseAnimation(ZipFile zip) - throws IOException, BootAnimationException { - if (zip == null) { - // To make tracking down boot animation problems we'll throw a BootAnimationException - // instead of an IllegalArgumentException. - throw new BootAnimationException("Boot animation ZipFile cannot be null"); - } - List animationParts = null; - - ZipEntry ze = zip.getEntry("desc.txt"); - if (ze != null) { - animationParts = parseDescription(zip.getInputStream(ze)); - } else { - throw new BootAnimationException("Missing desc.txt in root of bootanimation.zip"); - } - - if (animationParts == null) { - // We really should not end up here but in case we do here's an exception for ya! - throw new BootAnimationException("Unable to load boot animation."); - } - - Iterator iterator = animationParts.iterator(); - while(iterator.hasNext()) { - AnimationPart a = iterator.next(); - for (Enumeration e = zip.entries();e.hasMoreElements();) { - ze = e.nextElement(); - if (!ze.isDirectory() && ze.getName().contains(a.partName)) { - a.addFrame(ze.getName()); - } - } - if (a.frames.size() > 0) { - Collections.sort(a.frames); - } else { - // This boot animation may be salvageable if there are still some other parts - // that are good. We'll remove this part and if there are no parts left by - // the time we have iterated over all the parts then we can throw an exception. - Log.w(TAG, String.format("No frames in part %s, removing from animation", - a.partName)); - iterator.remove(); - } - } - if (animationParts.size() == 0) { - throw new BootAnimationException("Boot animation must have at least one part."); - } - - return animationParts; - } - - /** - * Parses the desc.txt of the boot animation - * @param in InputStream to the desc.txt - * @return A list of the parts as given in desc.txt - * @throws IOException - */ - private static List parseDescription(InputStream in) - throws IOException, BootAnimationException { - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - - // read in suggested width, height, and frame rate from first line - String line = reader.readLine(); - String[] details = line.split(" "); - if (details.length != NUM_FIRST_LINE_PARAMETERS) { - throw new BootAnimationException(String.format( - "Invalid # of parameters on first line of desc.txt; exptected %d, read %d " + - "(\"%s\")", - NUM_FIRST_LINE_PARAMETERS, details.length, line)); - } - - // The items should be in the following order: width, height, frame rate - final int width = Integer.parseInt(details[0]); - final int height = Integer.parseInt(details[1]); - final int frameRateMillis = 1000 / Integer.parseInt(details[2]); - - List animationParts = new ArrayList(); - while ((line = reader.readLine()) != null) { - // trim off any leading and trailing spaces - line = line.trim(); - // if the line is empty continue on to the next - if (TextUtils.isEmpty(line)) continue; - - String[] info = line. split(" "); - if (info.length != NUM_PART_LINE_PARAMETERS) { - Log.w(TAG, String.format( - "Invalid # of part parameters; exptected %d, read %d (\"%s\")", - NUM_PART_LINE_PARAMETERS, info.length, line)); - // let's continue in case there are parts that are valid - continue; - } - if (!info[0].equals("p") && !info[0].equals("c")) { - Log.w(TAG, String.format( - "Unknown part type; expected 'p' or 'c', read %s (\"%s\")", info[0], line)); - - // let's continue in case there are parts that are valid - continue; - } - int playCount = Integer.parseInt(info[1]); - int pause = Integer.parseInt(info[2]); - String name = info[3]; - AnimationPart ap = new AnimationPart(playCount, pause, name, frameRateMillis, - width, height); - animationParts.add(ap); - } - - return animationParts; - } - - public static String getPreviewFrameEntryName(InputStream is) throws IOException { - ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is - : new ZipInputStream(new BufferedInputStream(is)); - ZipEntry ze; - // First thing to do is iterate over all the entries and the zip and store them - // for building the animations afterwards - String previewName = null; - while ((ze = zis.getNextEntry()) != null) { - final String entryName = ze.getName(); - if (entryName.contains("/") - && (entryName.endsWith(".png") || entryName.endsWith(".jpg"))) { - previewName = entryName; - } - } - - return previewName; - } - - public static Bitmap loadPreviewFrame(Context context, InputStream is, String previewName) - throws IOException { - ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is - : new ZipInputStream(new BufferedInputStream(is)); - ZipEntry ze; - ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inSampleSize = am.isLowRamDevice() ? 4 : 2; - opts.inPreferredConfig = Bitmap.Config.RGB_565; - // First thing to do is iterate over all the entries and the zip and store them - // for building the animations afterwards - Bitmap preview = null; - while ((ze = zis.getNextEntry()) != null && preview == null) { - final String entryName = ze.getName(); - if (entryName.equals(previewName)) { - preview = BitmapFactory.decodeStream(zis, null, opts); - } - } - zis.close(); - - return preview; - } - - public static void clearBootAnimationCache(Context context) { - File cache = context.getCacheDir(); - if (cache.exists()) { - for(File f : cache.listFiles()) { - // volley stores stuff in cache so don't delete the volley directory - if(!f.isDirectory() && f.getName().endsWith(CACHED_SUFFIX)) f.delete(); - } - } - } - - public static class LoadBootAnimationImage extends AsyncTask { - private ImageView imv; - private String path; - private Context context; - - public LoadBootAnimationImage(ImageView imv, Context context, String path) { - this.imv = imv; - this.context = context; - this.path = path; - } - - @Override - protected Bitmap doInBackground(Object... params) { - Bitmap bitmap = null; - String previewName = null; - // this is ugly, ugly, ugly. Did I mention this is ugly? - try { - if (ThemeConfig.SYSTEM_DEFAULT.equals(path)) { - previewName = getPreviewFrameEntryName( - new FileInputStream(SYSTEM_BOOT_ANI_PATH)); - bitmap = loadPreviewFrame( - context, new FileInputStream(SYSTEM_BOOT_ANI_PATH), previewName); - } else { - final Context themeCtx = context.createPackageContext(path, 0); - previewName = getPreviewFrameEntryName( - themeCtx.getAssets().open("bootanimation/bootanimation.zip")); - bitmap = loadPreviewFrame(context, - themeCtx.getAssets().open("bootanimation/bootanimation.zip"), - previewName); - } - } catch (Exception e) { - // don't care since a null bitmap will be returned - e.printStackTrace(); - } - return bitmap; - } - - @Override - protected void onPostExecute(Bitmap result) { - if (result != null && imv != null) { - imv.setVisibility(View.VISIBLE); - imv.setImageBitmap(result); - } - } - } - - public static class BootAnimationException extends Exception { - public BootAnimationException(String detailMessage) { - super(detailMessage); - } - } -} diff --git a/src/com/cyngn/theme/util/CursorLoaderHelper.java b/src/com/cyngn/theme/util/CursorLoaderHelper.java deleted file mode 100644 index 5992779..0000000 --- a/src/com/cyngn/theme/util/CursorLoaderHelper.java +++ /dev/null @@ -1,433 +0,0 @@ -/* - * Copyright (C) 2015 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; - -import cyanogenmod.app.ThemeVersion; -import cyanogenmod.providers.ThemesContract; -import cyanogenmod.providers.ThemesContract.PreviewColumns; -import cyanogenmod.providers.ThemesContract.ThemesColumns; - -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; -import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; - -public class CursorLoaderHelper { - - public static final int LOADER_ID_INVALID = -1; - public static final int LOADER_ID_ALL = 0; - public static final int LOADER_ID_STATUS_BAR = 1; - public static final int LOADER_ID_FONT = 2; - public static final int LOADER_ID_ICONS = 3; - public static final int LOADER_ID_WALLPAPER = 4; - public static final int LOADER_ID_NAVIGATION_BAR = 5; - public static final int LOADER_ID_LOCKSCREEN = 6; - public static final int LOADER_ID_STYLE = 7; - public static final int LOADER_ID_BOOT_ANIMATION = 8; - public static final int LOADER_ID_RINGTONE = 9; - public static final int LOADER_ID_NOTIFICATION = 10; - public static final int LOADER_ID_ALARM = 11; - public static final int LOADER_ID_LIVE_LOCK_SCREEN = 12; - public static final int LOADER_ID_INSTALLED_THEMES = 1000; - public static final int LOADER_ID_APPLIED = 1001; - - private static final long DEFAULT_COMPONENT_ID = 0; - - private static int mThemeVersion = ThemeVersion.getVersion(); - - public static Loader chooserActivityCursorLoader(Context context, int id, - String appliedBaseTheme) { - String selection = null; - String selectionArgs[] = null; - String sortOrder = null; - String[] projection = null; - Uri contentUri = null; - - switch (id) { - case LOADER_ID_INSTALLED_THEMES: - selection = ThemesColumns.PRESENT_AS_THEME + "=? AND " + - ThemesColumns.INSTALL_STATE + "=?"; - selectionArgs = new String[] { "1", "" + ThemesColumns.InstallState.INSTALLED}; - // sort in ascending order but make sure the "default" theme is always first - sortOrder = "(" + ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, " - + "(" + ThemesColumns.PKG_NAME + "='" + appliedBaseTheme + "') DESC, " - + ThemesColumns.INSTALL_TIME + " DESC"; - contentUri = ThemesColumns.CONTENT_URI; - projection = new String[] {ThemesColumns.PKG_NAME, ThemesColumns.TITLE, - ThemesColumns.AUTHOR}; - break; - case LOADER_ID_APPLIED: - //TODO: Mix n match query should only be done once - contentUri = ThemesContract.MixnMatchColumns.CONTENT_URI; - selection = null; - selectionArgs = null; - break; - } - - return new CursorLoader(context, contentUri, projection, selection, - selectionArgs, sortOrder); - } - - public static Loader componentSelectorCursorLoader(Context context, int id) { - Uri uri = PreviewColumns.CONTENT_URI; - String selection; - String[] selectionArgs = { "1" }; - String[] projection = { ThemesColumns.TITLE, ThemesColumns.PKG_NAME }; - switch(id) { - case LOADER_ID_STATUS_BAR: - selection = MODIFIES_STATUS_BAR + "=?"; - projection = new String[] { - PreviewColumns.STATUSBAR_WIFI_ICON, - PreviewColumns.STATUSBAR_SIGNAL_ICON, - PreviewColumns.STATUSBAR_BLUETOOTH_ICON, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.STATUSBAR_BATTERY_CIRCLE, - PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, - PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - break; - case LOADER_ID_NAVIGATION_BAR: - selection = MODIFIES_NAVIGATION_BAR + "=?"; - projection = new String[] { - PreviewColumns.NAVBAR_BACK_BUTTON, - PreviewColumns.STATUSBAR_BACKGROUND, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME, - }; - break; - case LOADER_ID_FONT: - // fonts don't have generated previews so use the ThemesColumns.CONTENT_URI - uri = ThemesColumns.CONTENT_URI; - selection = MODIFIES_FONTS + "=?"; - break; - case LOADER_ID_ICONS: - selection = MODIFIES_ICONS + "=?"; - projection = new String[] { - PreviewColumns.ICON_PREVIEW_1, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - break; - case LOADER_ID_STYLE: - selection = MODIFIES_OVERLAYS + "=?"; - projection = new String[] { - PreviewColumns.STYLE_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - break; - case LOADER_ID_WALLPAPER: - selection = MODIFIES_LAUNCHER + "=?"; - if (mThemeVersion >= 3) { - uri = PreviewColumns.COMPONENTS_URI; - projection = new String[]{ - PreviewColumns.WALLPAPER_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME, - PreviewColumns.COMPONENT_ID - }; - } else { - projection = new String[]{ - PreviewColumns.WALLPAPER_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - } - break; - case LOADER_ID_BOOT_ANIMATION: - selection = MODIFIES_BOOT_ANIM + "=?"; - projection = new String[] { - PreviewColumns.BOOTANIMATION_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME - }; - break; - case LOADER_ID_RINGTONE: - selection = MODIFIES_RINGTONES + "=?"; - break; - case LOADER_ID_NOTIFICATION: - selection = MODIFIES_NOTIFICATIONS + "=?"; - break; - case LOADER_ID_ALARM: - selection = MODIFIES_ALARMS + "=?"; - break; - case LOADER_ID_LOCKSCREEN: - selection = MODIFIES_LOCKSCREEN + "=?"; - selectionArgs = new String[] { "1" }; - if (mThemeVersion >= 3) { - projection = new String[]{ - PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, - PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - PreviewColumns.COMPONENT_ID - }; - } else { - projection = new String[]{ - PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, - PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - ThemesColumns.PKG_NAME - }; - } - break; - case LOADER_ID_LIVE_LOCK_SCREEN: - selection = MODIFIES_LIVE_LOCK_SCREEN + "=?"; - selectionArgs = new String[] { "1" }; - if (mThemeVersion >= 3) { - projection = new String[]{ - PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.PKG_NAME, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - PreviewColumns.COMPONENT_ID - }; - } else { - projection = new String[]{ - PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, - PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, - ThemesColumns.TITLE, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - ThemesColumns.PKG_NAME - }; - } - break; - default: - return null; - } - // sort in ascending order but make sure the "default" theme is always first - String sortOrder = "(" + ThemesContract.ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, " - + ThemesContract.ThemesColumns.TITLE + " ASC"; - return new CursorLoader(context, uri, projection, selection, selectionArgs, sortOrder); - } - - public static Loader myThemeFragmentCursorLoader(Context context, int id) { - Uri uri; - String[] projection; - projection = new String[]{ - PreviewColumns.WALLPAPER_PREVIEW, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.STATUSBAR_WIFI_ICON, - PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, - PreviewColumns.STATUSBAR_BLUETOOTH_ICON, - PreviewColumns.STATUSBAR_SIGNAL_ICON, - PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, - PreviewColumns.STATUSBAR_BATTERY_CIRCLE, - PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, - PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, - PreviewColumns.NAVBAR_BACK_BUTTON, - PreviewColumns.NAVBAR_HOME_BUTTON, - PreviewColumns.NAVBAR_RECENT_BUTTON, - PreviewColumns.ICON_PREVIEW_1, - PreviewColumns.ICON_PREVIEW_2, - PreviewColumns.ICON_PREVIEW_3, - PreviewColumns.LOCK_WALLPAPER_PREVIEW, - PreviewColumns.STYLE_PREVIEW, - PreviewColumns.NAVBAR_BACKGROUND, - PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW - }; - uri = PreviewColumns.APPLIED_URI; - return new CursorLoader(context, uri, projection, null, null, null); - } - - public static Loader themeFragmentCursorLoader(Context context, int id, String pkgName, - long componentId) { - Uri uri = PreviewColumns.CONTENT_URI; - String selection = ThemesContract.ThemesColumns.PKG_NAME + "= ?"; - String[] selectionArgs = new String[] { pkgName }; - String[] projection = null; - switch (id) { - case LOADER_ID_ALL: - if (mThemeVersion >= 3) { - // Load all default component previews (component_id == 0) - selection += " AND " + PreviewColumns.COMPONENT_ID + "=?"; - selectionArgs = new String[] { pkgName, String.valueOf(DEFAULT_COMPONENT_ID) }; - } else { - // SQL query will fail if we ask for PreviewColumns.COMPONENT_ID, don't add it. - selectionArgs = new String[]{pkgName}; - } - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - ThemesColumns.AUTHOR, - ThemesColumns.WALLPAPER_URI, - ThemesColumns.HOMESCREEN_URI, - ThemesColumns.TARGET_API, - // Theme abilities - ThemesColumns.MODIFIES_LAUNCHER, - ThemesColumns.MODIFIES_LOCKSCREEN, - ThemesColumns.MODIFIES_ALARMS, - ThemesColumns.MODIFIES_BOOT_ANIM, - ThemesColumns.MODIFIES_FONTS, - ThemesColumns.MODIFIES_ICONS, - ThemesColumns.MODIFIES_NAVIGATION_BAR, - ThemesColumns.MODIFIES_OVERLAYS, - ThemesColumns.MODIFIES_RINGTONES, - ThemesColumns.MODIFIES_STATUS_BAR, - ThemesColumns.MODIFIES_NOTIFICATIONS, - //Previews - PreviewColumns.WALLPAPER_PREVIEW, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.STATUSBAR_WIFI_ICON, - PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, - PreviewColumns.STATUSBAR_BLUETOOTH_ICON, - PreviewColumns.STATUSBAR_SIGNAL_ICON, - PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, - PreviewColumns.STATUSBAR_BATTERY_CIRCLE, - PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, - PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, - PreviewColumns.NAVBAR_BACK_BUTTON, - PreviewColumns.NAVBAR_HOME_BUTTON, - PreviewColumns.NAVBAR_RECENT_BUTTON, - PreviewColumns.ICON_PREVIEW_1, - PreviewColumns.ICON_PREVIEW_2, - PreviewColumns.ICON_PREVIEW_3, - PreviewColumns.LOCK_WALLPAPER_PREVIEW, - PreviewColumns.STYLE_PREVIEW, - PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW - }; - break; - case LOADER_ID_STATUS_BAR: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.STATUSBAR_WIFI_ICON, - PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, - PreviewColumns.STATUSBAR_BLUETOOTH_ICON, - PreviewColumns.STATUSBAR_SIGNAL_ICON, - PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, - PreviewColumns.STATUSBAR_BATTERY_CIRCLE, - PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, - PreviewColumns.STATUSBAR_BATTERY_PORTRAIT - }; - break; - case LOADER_ID_FONT: - uri = ThemesColumns.CONTENT_URI; - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE - }; - break; - case LOADER_ID_ICONS: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.ICON_PREVIEW_1, - PreviewColumns.ICON_PREVIEW_2, - PreviewColumns.ICON_PREVIEW_3, - }; - break; - case LOADER_ID_WALLPAPER: - if (mThemeVersion >= 3) { - uri = PreviewColumns.COMPONENTS_URI; - // Load specified wallpaper previews (component_id is specified) - selection += " AND " + PreviewColumns.COMPONENT_ID + "=?"; - selectionArgs = new String[]{pkgName, String.valueOf(componentId)}; - projection = new String[]{ - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.WALLPAPER_PREVIEW, - PreviewColumns.COMPONENT_ID - }; - } else { - projection = new String[]{ - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.WALLPAPER_PREVIEW - }; - } - break; - case LOADER_ID_NAVIGATION_BAR: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.STATUSBAR_BACKGROUND, - PreviewColumns.NAVBAR_BACK_BUTTON, - PreviewColumns.NAVBAR_HOME_BUTTON, - PreviewColumns.NAVBAR_RECENT_BUTTON - }; - break; - case LOADER_ID_LOCKSCREEN: - projection = new String[]{ - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.LOCK_WALLPAPER_PREVIEW, - }; - break; - case LOADER_ID_LIVE_LOCK_SCREEN: - projection = new String[]{ - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, - PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW - }; - break; - case LOADER_ID_STYLE: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE, - PreviewColumns.STYLE_PREVIEW - }; - break; - case LOADER_ID_BOOT_ANIMATION: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE - }; - break; - case LOADER_ID_RINGTONE: - case LOADER_ID_NOTIFICATION: - case LOADER_ID_ALARM: - projection = new String[] { - ThemesColumns.PKG_NAME, - ThemesColumns.TITLE - }; - break; - } - return new CursorLoader(context, uri, projection, selection, selectionArgs, null); - } - - public static Object[] getRowFromCursor(Cursor cursor) { - Object[] row = null; - if (cursor != null) { - int colCount = cursor.getColumnCount(); - row = new Object[colCount]; - for (int indx = 0; indx < colCount; indx++) { - row[indx] = getFieldValueFromRow(cursor, indx); - } - } - return row; - } - - public static Object getFieldValueFromRow(Cursor cursor, int position) { - switch (cursor.getType(position)) { - case Cursor.FIELD_TYPE_BLOB: return cursor.getBlob(position); - case Cursor.FIELD_TYPE_FLOAT: return cursor.getFloat(position); - case Cursor.FIELD_TYPE_INTEGER: return cursor.getInt(position); - case Cursor.FIELD_TYPE_STRING: return cursor.getString(position); - case Cursor.FIELD_TYPE_NULL: - default: - return null; - } - } -} \ No newline at end of file diff --git a/src/com/cyngn/theme/util/FontConfigParser.java b/src/com/cyngn/theme/util/FontConfigParser.java deleted file mode 100644 index ae963b8..0000000 --- a/src/com/cyngn/theme/util/FontConfigParser.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.util.Xml; - -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * Parses an XML font config. Example: - * - * - * - * - * - * sans-serif - * arial - * - * - * Roboto-Regular.ttf - * Roboto-Bold.ttf - * Roboto-Italic.ttf - * Roboto-BoldItalic.ttf - * - * - * - * ... - * - * - */ -public class FontConfigParser { - - public static class Family { - public List nameset = new ArrayList(); - public List fileset = new ArrayList(); - } - - public static List parse(InputStream in) throws XmlPullParserException, IOException { - try { - XmlPullParser parser = Xml.newPullParser(); - parser.setInput(in, null); - parser.nextTag(); - return readFamilySet(parser); - } finally { - in.close(); - } - } - - private static List readFamilySet(XmlPullParser parser) throws XmlPullParserException, IOException { - List families = new ArrayList(); - parser.require(XmlPullParser.START_TAG, null, "familyset"); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - String name = parser.getName(); - - // Starts by looking for the entry tag - if (name.equals("family")) { - Family family = readFamily(parser); - families.add(family); - } - } - return families; - } - - private static Family readFamily(XmlPullParser parser) throws XmlPullParserException, IOException { - Family family = new Family(); - parser.require(XmlPullParser.START_TAG, null, "family"); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - String name = parser.getName(); - if (name.equals("nameset")) { - List nameset = readNameset(parser); - family.nameset = nameset; - } else if (name.equals("fileset")) { - List fileset = readFileset(parser); - family.fileset = fileset; - } else { - skip(parser); - } - } - return family; - } - - private static List readNameset(XmlPullParser parser) throws XmlPullParserException, IOException { - List names = new ArrayList(); - parser.require(XmlPullParser.START_TAG, null, "nameset"); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - String tagname = parser.getName(); - if (tagname.equals("name")) { - String name = readText(parser); - names.add(name); - } else { - skip(parser); - } - } - return names; - } - - private static List readFileset(XmlPullParser parser) throws XmlPullParserException, IOException { - List files = new ArrayList(); - parser.require(XmlPullParser.START_TAG, null, "fileset"); - - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() != XmlPullParser.START_TAG) { - continue; - } - String name = parser.getName(); - if (name.equals("file")) { - String file = readText(parser); - files.add(file); - } else { - skip(parser); - } - } - return files; - } - - // For the tags title and summary, extracts their text values. - private static String readText(XmlPullParser parser) throws IOException, XmlPullParserException { - String result = ""; - if (parser.next() == XmlPullParser.TEXT) { - result = parser.getText(); - parser.nextTag(); - } - return result; - } - - private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { - if (parser.getEventType() != XmlPullParser.START_TAG) { - throw new IllegalStateException(); - } - int depth = 1; - while (depth != 0) { - switch (parser.next()) { - case XmlPullParser.END_TAG: - depth--; - break; - case XmlPullParser.START_TAG: - depth++; - break; - } - } - } -} diff --git a/src/com/cyngn/theme/util/IconPreviewHelper.java b/src/com/cyngn/theme/util/IconPreviewHelper.java deleted file mode 100644 index 60ef9c8..0000000 --- a/src/com/cyngn/theme/util/IconPreviewHelper.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.ActivityManager; -import android.app.ComposedIconInfo; -import android.app.IconPackHelper; -import android.content.ComponentName; -import android.content.Context; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageItemInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.SparseArray; - -/** - * This class handles all the logic to build a preview icon - * If the system currently has a theme applied we do NOT - * want this code to be impacted by it. So code in this - * class creates special "no theme attached" resource objects - * to retrieve objects from. - */ -public class IconPreviewHelper { - private static final String TAG = IconPreviewHelper.class.getSimpleName(); - private final static float ICON_SCALE_FACTOR = 1.3f; //Arbitrary. Looks good - - private Context mContext; - private DisplayMetrics mDisplayMetrics; - private Configuration mConfiguration; - private int mIconDpi = 0; - private String mThemePkgName; - private IconPackHelper mIconPackHelper; - private int mIconSize; - - /** - * @param themePkgName - The package name of the theme we wish to preview - */ - public IconPreviewHelper(Context context, String themePkgName) { - mContext = context; - mDisplayMetrics = context.getResources().getDisplayMetrics(); - mConfiguration = context.getResources().getConfiguration(); - ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - mIconDpi = (int) (am.getLauncherLargeIconDensity() * ICON_SCALE_FACTOR); - mThemePkgName = themePkgName; - mIconPackHelper = new IconPackHelper(mContext); - try { - mIconPackHelper.loadIconPack(mThemePkgName); - } catch (NameNotFoundException e) {} - mIconSize = (int) (am.getLauncherLargeIconSize() * ICON_SCALE_FACTOR); - } - - /** - * Returns the actual label name for a given component - * If the activity does not have a label it will return app's label - * If neither has a label returns empty string - */ - public String getLabel(ComponentName component) { - String label = ""; - try { - PackageManager pm = mContext.getPackageManager(); - ApplicationInfo appInfo = pm.getApplicationInfo(component.getPackageName(), 0); - ActivityInfo activityInfo = pm.getActivityInfo(component, 0); - - AssetManager assets = new AssetManager(); - assets.addAssetPath(appInfo.publicSourceDir); - Resources res = new Resources(assets, mDisplayMetrics, mConfiguration); - - if (activityInfo.labelRes != 0) { - label = res.getString(activityInfo.labelRes); - } else if (appInfo.labelRes != 0) { - label = res.getString(appInfo.labelRes); - } - } catch(NameNotFoundException exception) { - Log.e(TAG, "unable to find pkg for " + component.toString()); - } - return label; - } - - /** - * Returns the icon for the given component regardless of the system's - * currently applied theme. If the preview theme does not support the icon, then - * return the system default icon. - */ - public Drawable getIcon(ComponentName component) { - String packageName = component.getPackageName(); - String activityName = component.getClassName(); - Drawable icon = getThemedIcon(packageName, activityName); - if (icon == null) { - icon = getDefaultIcon(packageName, activityName); - } - if (icon != null) { - icon.setBounds(0, 0, mIconSize, mIconSize); - } - return icon; - } - - private Drawable getThemedIcon(String pkgName, String activityName) { - Drawable drawable = null; - ActivityInfo info = new ActivityInfo(); - info.packageName = pkgName; - info.name = activityName; - drawable = mIconPackHelper.getDrawableForActivityWithDensity(info, mIconDpi); - - return drawable; - } - - /** - * Returns the default icon. This can be the normal icon associated with the app or a composed - * icon if the icon pack supports background, mask, and/or foreground. - * @param pkgName - * @param activityName - * @return - */ - public Drawable getDefaultIcon(String pkgName, String activityName) { - Drawable drawable = null; - ComponentName component = new ComponentName(pkgName, activityName); - PackageManager pm = mContext.getPackageManager(); - Resources res = null; - try { - ActivityInfo info = pm.getActivityInfo(component, 0); - ApplicationInfo appInfo = pm.getApplicationInfo(pkgName, 0); - - AssetManager assets = new AssetManager(); - assets.addAssetPath(appInfo.publicSourceDir); - res = new Resources(assets, mDisplayMetrics, mConfiguration); - - final int iconId = info.icon != 0 ? info.icon : appInfo.icon; - info.themedIcon = 0; - setupComposedIcon(res, info, iconId); - drawable = getFullResIcon(res, iconId); - } catch (NameNotFoundException e2) { - Log.w(TAG, "Unable to get the icon for " + pkgName + " using default"); - } - drawable = (drawable != null) ? - getComposedIcon(res, drawable) : getFullResDefaultActivityIcon(); - return drawable; - } - - private Drawable getComposedIcon(Resources res, Drawable baseIcon) { - ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo(); - if (res != null && iconInfo != null && (iconInfo.iconBacks != null || - iconInfo.iconMask != 0 || iconInfo.iconUpon != 0)) { - return IconPackHelper.IconCustomizer.getComposedIconDrawable(baseIcon, res, iconInfo); - } - return baseIcon; - } - - private void setupComposedIcon(Resources res, ActivityInfo info, int iconId) { - ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo(); - if (iconInfo.iconBacks == null && iconInfo.iconMask == 0 && iconInfo.iconUpon == 0) { - return; - } - - res.setComposedIconInfo(iconInfo); - - SparseArray icons = new SparseArray(1); - info.themedIcon = 0; - icons.put(iconId, info); - res.setIconResources(icons); - } - - private Drawable getFullResIcon(Resources resources, int iconId) { - Drawable d; - try { - d = resources.getDrawableForDensity(iconId, mIconDpi, null, false); - } catch (Resources.NotFoundException e) { - d = null; - } - return (d != null) ? d : getFullResDefaultActivityIcon(); - } - - private Drawable getFullResDefaultActivityIcon() { - return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon); - } -} diff --git a/src/com/cyngn/theme/util/NotificationHelper.java b/src/com/cyngn/theme/util/NotificationHelper.java deleted file mode 100644 index 3ae68d7..0000000 --- a/src/com/cyngn/theme/util/NotificationHelper.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.graphics.BitmapFactory; -import android.text.TextUtils; - -import com.cyngn.theme.chooser.ChooserActivity; -import com.cyngn.theme.chooser.R; - -public class NotificationHelper { - private static final int NOTIFICATION_ID = 0x434D5443; - - public static void postThemeInstalledNotification(Context context, String pkgName) { - String themeName = null; - try { - PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); - if (pi.themeInfo != null) { - themeName = pi.themeInfo.name; - } - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return; - } - if (TextUtils.isEmpty(themeName)) { - return; - } - - int themeCount = PreferenceUtils.getNewlyInstalledThemeCount(context) + 1; - - Intent intent = new Intent(context, ChooserActivity.class); - intent.setAction(Intent.ACTION_MAIN); - intent.putExtra("pkgName", pkgName); - PendingIntent pi = PendingIntent.getActivity(context, 0, intent, - PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); - - String title = null; - String content = null; - final Resources res = context.getResources(); - if (themeCount == 1) { - title = String.format(res.getString( - R.string.theme_installed_notification_title), themeName); - content = res.getString(R.string.theme_installed_notification_text); - } else { - title = String.format(res.getString(R.string.themes_installed_notification_title), - themeCount); - content = String.format(res.getQuantityString( - R.plurals.themes_installed_notification_text, themeCount -1), - themeName, themeCount - 1); - } - NotificationManager nm = - (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - Notification notice = new Notification.Builder(context) - .setAutoCancel(true) - .setOngoing(false) - .setContentTitle(title) - .setContentText(content) - .setContentIntent(pi) - .setSmallIcon(R.drawable.ic_notify) - .setWhen(System.currentTimeMillis()) - .build(); - if (themeCount > 1) notice.number = themeCount; - nm.notify(NOTIFICATION_ID, notice); - PreferenceUtils.setNewlyInstalledThemeCount(context, themeCount); - } - - public static void cancelNotifications(Context context) { - NotificationManager nm = (NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel(NOTIFICATION_ID); - PreferenceUtils.setNewlyInstalledThemeCount(context, 0); - } -} diff --git a/src/com/cyngn/theme/util/PreferenceUtils.java b/src/com/cyngn/theme/util/PreferenceUtils.java deleted file mode 100644 index 6444c41..0000000 --- a/src/com/cyngn/theme/util/PreferenceUtils.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.util; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.ThemeUtils; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.text.TextUtils; -import android.util.Log; - -import java.util.HashSet; -import java.util.Set; - -public class PreferenceUtils { - private static final String TAG = PreferenceUtils.class.getSimpleName(); - - public static final String PREF_APPLIED_BASE_THEME = "applied_base_theme"; - public static final String PREF_UPDATED_THEMES = "updated_themes"; - public static final String PREF_NEWLY_INSTALLED_THEME_COUNT = "newly_installed_theme_count"; - public static final String PREF_INSTALLED_THEMES_PROCESSING = "installed_themes_processing"; - public static final String PREF_SHOW_PER_APP_THEMING_NEW_TAG = "show_per_app_new_tag"; - - public static SharedPreferences getSharedPreferences(Context context) { - if (context == null) return null; - return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); - } - - public static String getAppliedBaseTheme(Context context) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs == null) return null; - - final Resources res = context.getResources(); - final ThemeConfig config = res.getConfiguration().themeConfig; - String appliedTheme = config != null - ? config.getOverlayPkgName() - : ThemeConfig.SYSTEM_DEFAULT; - return prefs.getString(PREF_APPLIED_BASE_THEME, appliedTheme); - } - - public static void setAppliedBaseTheme(Context context, String pkgName) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - prefs.edit().putString(PREF_APPLIED_BASE_THEME, pkgName).apply(); - } - } - - public static Set getUpdatedThemes(Context context) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs == null) return null; - - return prefs.getStringSet(PREF_UPDATED_THEMES, null); - } - - public static void addUpdatedTheme(Context context, String pkgName) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - Set updatedThemes = new HashSet(); - Set current = prefs.getStringSet(PREF_UPDATED_THEMES, null); - if (current != null) { - updatedThemes.addAll(current); - } - if (updatedThemes.add(pkgName)) { - prefs.edit().putStringSet(PREF_UPDATED_THEMES, updatedThemes).apply(); - } - } - } - - public static void removeUpdatedTheme(Context context, String pkgName) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - Set updatedThemes = new HashSet(); - Set current = prefs.getStringSet(PREF_UPDATED_THEMES, null); - if (current != null) { - updatedThemes.addAll(current); - } - if (updatedThemes.remove(pkgName)) { - prefs.edit().putStringSet(PREF_UPDATED_THEMES, updatedThemes).apply(); - } - } - } - - public static boolean hasThemeBeenUpdated(Context context, String pkgName) { - Set updatedThemes = getUpdatedThemes(context); - return updatedThemes != null && updatedThemes.contains(pkgName); - } - - public static int getNewlyInstalledThemeCount(Context context) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs == null) return 0; - - return prefs.getInt(PREF_NEWLY_INSTALLED_THEME_COUNT, 0); - } - - public static void setNewlyInstalledThemeCount(Context context, int count) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - prefs.edit().putInt(PREF_NEWLY_INSTALLED_THEME_COUNT, count).apply(); - } - } - - public static boolean getShowPerAppThemeNewTag(Context context) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - return prefs.getBoolean(PREF_SHOW_PER_APP_THEMING_NEW_TAG, true); - } - - return false; - } - - public static void setShowPerAppThemeNewTag(Context context, boolean show) { - SharedPreferences prefs = getSharedPreferences(context); - if (prefs != null) { - prefs.edit().putBoolean(PREF_SHOW_PER_APP_THEMING_NEW_TAG, show).apply(); - } - } -} diff --git a/src/com/cyngn/theme/util/ThemedTypefaceHelper.java b/src/com/cyngn/theme/util/ThemedTypefaceHelper.java deleted file mode 100644 index ce38f80..0000000 --- a/src/com/cyngn/theme/util/ThemedTypefaceHelper.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.content.Context; -import android.content.res.AssetManager; -import android.graphics.FontListParser; -import android.graphics.FontListParser.Family; -import android.graphics.Typeface; -import android.util.Log; - -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.List; - -/** - * Assists in loading a themes font typefaces. - * Will load system default if there is a load issue - */ -public class ThemedTypefaceHelper { - private static final String TAG = ThemedTypefaceHelper.class.getName(); - private static final String FAMILY_SANS_SERIF = "sans-serif"; - private static final String FONTS_DIR = "fonts"; - private static final String SYSTEM_FONTS_XML = "/system/etc/system_fonts.xml"; - private static final String SYSTEM_FONTS_DIR = "/system/fonts"; - - private boolean mIsLoaded; - private Context mThemeContext; - private List mFamilies; - private Typeface[] mTypefaces = new Typeface[4]; - - public void load(Context context, String pkgName) { - try { - loadThemedFonts(context, pkgName); - return; - } catch(Exception e) { - Log.w(TAG, "Unable to parse and load themed fonts for " + pkgName + - ". Falling back to system fonts", e ); - } - - try { - loadSystemFonts(); - return; - } catch(Exception e) { - Log.e(TAG, "Parsing system fonts failed. Falling back to Typeface loaded fonts", e); - } - - // There is no reason for this to happen unless someone - // messed up the system_fonts.xml - loadDefaultFonts(); - } - - private void loadThemedFonts(Context context, String pkgName) throws Exception { - //Parse the font XML - mThemeContext = context.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY); - AssetManager assetManager = mThemeContext.getAssets(); - InputStream is = assetManager.open(FONTS_DIR + File.separator + ThemeUtils.FONT_XML); - FontListParser.Config fontConfig = FontListParser.parse(is, FONTS_DIR); - mFamilies = fontConfig.families; - - //Load the typefaces for sans-serif - Family sanSerif = getFamily(FAMILY_SANS_SERIF); - mTypefaces[Typeface.NORMAL] = loadTypeface(sanSerif, Typeface.NORMAL); - mTypefaces[Typeface.BOLD] = loadTypeface(sanSerif, Typeface.BOLD); - mTypefaces[Typeface.ITALIC] = loadTypeface(sanSerif, Typeface.ITALIC); - mTypefaces[Typeface.BOLD_ITALIC] = loadTypeface(sanSerif, Typeface.BOLD_ITALIC); - mIsLoaded = true; - } - - private void loadSystemFonts() throws Exception { - //Parse the system font XML - File file = new File(SYSTEM_FONTS_XML); - InputStream is = new FileInputStream(file); - FontListParser.Config fontConfig = FontListParser.parse(is, SYSTEM_FONTS_DIR); - mFamilies = fontConfig.families; - - //Load the typefaces for sans-serif - Family sanSerif = getFamily(FAMILY_SANS_SERIF); - if (mTypefaces[Typeface.NORMAL] == null) { - mTypefaces[Typeface.NORMAL] = loadSystemTypeface(sanSerif, Typeface.NORMAL); - } - if (mTypefaces[Typeface.BOLD] == null) { - mTypefaces[Typeface.BOLD] = loadSystemTypeface(sanSerif, Typeface.BOLD); - } - if (mTypefaces[Typeface.ITALIC] == null) { - mTypefaces[Typeface.ITALIC] = loadSystemTypeface(sanSerif, Typeface.ITALIC); - } - if (mTypefaces[Typeface.BOLD_ITALIC] == null) { - mTypefaces[Typeface.BOLD_ITALIC] = loadSystemTypeface(sanSerif, Typeface.BOLD_ITALIC); - } - mIsLoaded = true; - } - - private void loadDefaultFonts() { - mTypefaces[Typeface.NORMAL] = Typeface.DEFAULT; - mTypefaces[Typeface.BOLD] = Typeface.DEFAULT_BOLD; - mIsLoaded = true; - } - - private Family getFamily(String familyName) throws Exception { - for(Family family : mFamilies) { - if (family.name.equals(familyName)) { - return family; - } - } - throw new Exception("Unable to find " + familyName); - } - - private Typeface loadTypeface(Family family, int style) { - AssetManager assets = mThemeContext.getAssets(); - String path = family.fonts.get(style).fontName; - return Typeface.createFromAsset(assets, path); - } - - private Typeface loadSystemTypeface(Family family, int style) { - return Typeface.createFromFile(family.fonts.get(style).fontName); - } - - public Typeface getTypeface(int style) { - if (!mIsLoaded) throw new IllegalStateException("Helper was not loaded"); - return mTypefaces[style]; - } -} diff --git a/src/com/cyngn/theme/util/TypefaceHelperCache.java b/src/com/cyngn/theme/util/TypefaceHelperCache.java deleted file mode 100644 index ced93be..0000000 --- a/src/com/cyngn/theme/util/TypefaceHelperCache.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.content.Context; - -import java.util.HashMap; -import java.util.Map; - -public class TypefaceHelperCache { - private static TypefaceHelperCache sHelperCache; - private final Map mCache; - - private TypefaceHelperCache() { - mCache = new HashMap(); - } - - public static synchronized TypefaceHelperCache getInstance() { - if (sHelperCache == null) { - sHelperCache = new TypefaceHelperCache(); - } - return sHelperCache; - } - - public ThemedTypefaceHelper getHelperForTheme(Context context, String pkgName) { - synchronized (mCache) { - ThemedTypefaceHelper helper = mCache.get(pkgName); - if (helper == null) { - helper = new ThemedTypefaceHelper(); - helper.load(context, pkgName); - mCache.put(pkgName, helper); - } - return helper; - } - } - - public int getTypefaceCount() { - synchronized (mCache) { - return mCache.size(); - } - } -} diff --git a/src/com/cyngn/theme/util/Utils.java b/src/com/cyngn/theme/util/Utils.java deleted file mode 100644 index a0b910d..0000000 --- a/src/com/cyngn/theme/util/Utils.java +++ /dev/null @@ -1,710 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.ActivityManager; -import android.app.WallpaperManager; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PermissionInfo; -import android.content.pm.ResolveInfo; -import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.ThemeConfig; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.RemoteException; -import android.provider.Settings; -import android.text.TextUtils; -import android.util.Log; -import android.util.TypedValue; -import android.view.IWindowManager; -import android.view.WindowManager; -import android.view.WindowManagerGlobal; - -import com.cyngn.theme.chooser.ChooserActivity; - -import cyanogenmod.externalviews.KeyguardExternalView; -import cyanogenmod.providers.CMSettings; -import cyanogenmod.providers.ThemesContract; - -import org.cyanogenmod.internal.util.ThemeUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Field; -import java.security.InvalidParameterException; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import static android.content.res.ThemeConfig.SYSTEM_DEFAULT; - -public class Utils { - private static final String TAG = Utils.class.getSimpleName(); - private static final boolean DEBUG = false; - - private static final String OVERLAY_BASE_PATH = "overlays" + File.separator; - - public static Bitmap decodeFile(String path, int reqWidth, int reqHeight) { - BitmapFactory.Options opts = new BitmapFactory.Options(); - - // Determine insample size - opts.inJustDecodeBounds = true; - BitmapFactory.decodeFile(path, opts); - opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); - - // Decode the bitmap, regionally if necessary - Bitmap bitmap = null; - opts.inJustDecodeBounds = false; - Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); - try { - if (rect != null) { - BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(path, false); - // Check if we can downsample more now that we cropped - opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), - reqWidth, reqHeight); - bitmap = decoder.decodeRegion(rect, opts); - } else { - bitmap = BitmapFactory.decodeFile(path, opts); - } - } catch (IOException e) { - Log.e(TAG, "Unable to open resource in path" + path, e); - } - return bitmap; - } - - public static Bitmap decodeResource(Resources res, int resId, int reqWidth, int reqHeight) { - BitmapFactory.Options opts = new BitmapFactory.Options(); - - // Determine insample size - opts.inJustDecodeBounds = true; - BitmapFactory.decodeResource(res, resId, opts); - opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); - - // Decode the bitmap, regionally if necessary - Bitmap bitmap = null; - opts.inJustDecodeBounds = false; - Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); - - InputStream stream = null; - try { - if (rect != null) { - stream = res.openRawResource(resId, new TypedValue()); - if (stream == null) return null; - BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(stream, false); - // Check if we can downsample a little more now that we cropped - opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), - reqWidth, reqHeight); - bitmap = decoder.decodeRegion(rect, opts); - } else { - bitmap = BitmapFactory.decodeResource(res, resId, opts); - } - } catch (IOException e) { - Log.e(TAG, "Unable to open resource " + resId, e); - } finally { - closeQuiet(stream); - } - return bitmap; - } - - - public static Bitmap getBitmapFromAsset(Context ctx, String path,int reqWidth, int reqHeight) { - if (ctx == null || path == null) - return null; - - String ASSET_BASE = "file:///android_asset/"; - path = path.substring(ASSET_BASE.length()); - - - Bitmap bitmap = null; - try { - AssetManager assets = ctx.getAssets(); - InputStream is = assets.open(path); - - // Determine insample size - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inJustDecodeBounds = true; - BitmapFactory.decodeStream(is, null, opts); - opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); - is.close(); - - // Decode the bitmap, regionally if neccessary - is = assets.open(path); - opts.inJustDecodeBounds = false; - Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); - if (rect != null) { - BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false); - // Check if we can downsample a little more now that we cropped - opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), - reqWidth, reqHeight); - bitmap = decoder.decodeRegion(rect, opts); - } else { - bitmap = BitmapFactory.decodeStream(is); - } - } catch (IOException e) { - e.printStackTrace(); - } - return bitmap; - } - - - /** - * For excessively large images with an awkward ratio we - * will want to crop them - * @return - */ - public static Rect getCropRectIfNecessary( - BitmapFactory.Options options,int reqWidth, int reqHeight) { - Rect rect = null; - // Determine downsampled size - int width = options.outWidth / options.inSampleSize; - int height = options.outHeight / options.inSampleSize; - - if ((reqHeight * 1.5 < height)) { - int bottom = height/ 4; - int top = bottom + height/2; - rect = new Rect(0, bottom, width, top); - } else if ((reqWidth * 1.5 < width)) { - int left = width / 4; - int right = left + height/2; - rect = new Rect(left, 0, right, height); - } - return rect; - } - - public static int calculateInSampleSize( - BitmapFactory.Options options, int reqWidth, int reqHeight) { - return calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight); - } - - // Modified from original source: - // http://developer.android.com/training/displaying-bitmaps/load-bitmap.html - public static int calculateInSampleSize( - int decodeWidth, int decodeHeight, int reqWidth, int reqHeight) { - // Raw height and width of image - int inSampleSize = 1; - - if (decodeHeight > reqHeight || decodeWidth > reqWidth) { - final int halfHeight = decodeHeight / 2; - final int halfWidth = decodeWidth / 2; - - // Calculate the largest inSampleSize value that is a power of 2 and keeps both - // height and width larger than the requested height and width. - while ((halfHeight / inSampleSize) > reqHeight && - (halfWidth / inSampleSize) > reqWidth) { - inSampleSize *= 2; - } - } - - return inSampleSize; - } - - public static InputStream getInputStreamFromAsset( - Context ctx, String path) throws IOException { - if (ctx == null || path == null) - return null; - InputStream is = null; - String ASSET_BASE = "file:///android_asset/"; - path = path.substring(ASSET_BASE.length()); - AssetManager assets = ctx.getAssets(); - is = assets.open(path); - return is; - } - - public static void copy(InputStream is, OutputStream os) throws IOException { - final byte[] bytes = new byte[4096]; - int len; - while ((len = is.read(bytes)) > 0) { - os.write(bytes, 0, len); - } - } - - public static void closeQuiet(InputStream stream) { - if (stream == null) - return; - try { - stream.close(); - } catch (IOException e) { - } - } - - public static void closeQuiet(OutputStream stream) { - if (stream == null) - return; - try { - stream.close(); - } catch (IOException e) { - } - } - - //Note: will not delete populated subdirs - public static void deleteFilesInDir(String dirPath) { - File fontDir = new File(dirPath); - File[] files = fontDir.listFiles(); - if (files != null) { - for(File file : fontDir.listFiles()) { - file.delete(); - } - } - } - - public static boolean hasNavigationBar(Context context) { - boolean needsNavigationBar = false; - try { - IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); - needsNavigationBar = wm.needsNavigationBar(); - } catch (RemoteException e) { - } - // Need to also check for devices with hardware keys where the user has chosen to use - // the on screen navigation bar - needsNavigationBar = needsNavigationBar || - CMSettings.Secure.getInt(context.getContentResolver(), - CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR, 0) == 1; - return needsNavigationBar; - } - - public static Bitmap loadBitmapBlob(Cursor cursor, int columnIdx) { - if (columnIdx < 0) { - Log.w(TAG, "loadBitmapBlob(): Invalid index provided, returning null"); - return null; - } - - if (cursor.getType(columnIdx) == Cursor.FIELD_TYPE_STRING) { - return loadBitmapFile(cursor, columnIdx); - } - - byte[] blob = cursor.getBlob(columnIdx); - if (blob == null) return null; - return BitmapFactory.decodeByteArray(blob, 0, blob.length); - } - - public static Bitmap loadBitmapFile(Cursor cursor, int columnIdx) { - if (columnIdx < 0) { - Log.w(TAG, "loadBitmapFile(): Invalid index provided, returning null"); - return null; - } - String path = cursor.getString(columnIdx); - if (TextUtils.isEmpty(path)) { - return null; - } - - Bitmap image = null; - FileInputStream inputStream; - try { - inputStream = new FileInputStream(path); - image = BitmapFactory.decodeStream(inputStream); - inputStream.close(); - } catch (Exception e) { - Log.w(TAG, "Unable to open preview " + path, e); - } - - return image; - } - - public static String getBatteryIndex(int type) { - switch(type) { - case 2: - return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_CIRCLE; - case 5: - return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE; - default: - return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_PORTRAIT; - } - } - - public static Bitmap getRegularWallpaperBitmap(Context context) { - WallpaperManager wm = WallpaperManager.getInstance(context); - - Bitmap bitmap = null; - // desktop wallpaper here - Bitmap wallpaper = wm.getBitmap(); - if (wallpaper == null) { - return null; - } - - Point size = new Point(); - WindowManager windowManager = - (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); - windowManager.getDefaultDisplay().getRealSize(size); - - final int dw = size.x; - final int dh = size.y; - - // Center the scaled image - float scale = Math.max(1f, Math.max(dw / (float) wallpaper.getWidth(), - dh / (float) wallpaper.getHeight())); - - final int scaledWidth = Math.round((wallpaper.getWidth() * scale)); - final int scaledHeight = Math.round((wallpaper.getHeight() * scale)); - - // TODO: set xOffset to wm.getLastWallpaperX() once available - int xOffset = wm.getLastWallpaperX(); - // x offset - if (xOffset == -1) { - xOffset = 0; - } else { - xOffset *= -1; - } - - // y offsets - // TODO: set yOffset to wm.getLastWallpaperY() once available - int yOffset = wm.getLastWallpaperY(); - if (yOffset == -1) { - yOffset = 0; - } else { - yOffset *= -1; - } - - if (DEBUG) { - Log.d(TAG, "scale: " + scale); - Log.d(TAG, "scaledWidth: " + scaledWidth); - Log.d(TAG, "scaledHeight: " + scaledHeight); - Log.d(TAG, "wallpaper size: width: " + wallpaper.getWidth() + - ", height: " + wallpaper.getHeight()); - Log.d(TAG, "xOffset: " + xOffset); - Log.d(TAG, "yOffset: " + yOffset); - } - - try { - if (wallpaper.getHeight() < dh) { - // need to scale it up vertically - - if (wallpaper.getHeight() > wallpaper.getWidth()) { - // handle portrait wallpaper - float diff = scaledWidth - dw; - int diffhalf = Math.round(diff / 2); - - bitmap = Bitmap.createScaledBitmap(wallpaper, scaledWidth, scaledHeight, true); - bitmap = Bitmap.createBitmap(bitmap, diffhalf, 0, dw, dh); - bitmap = Bitmap.createBitmap(bitmap, xOffset, 0, dw, dh); - } else { - int goldenWidth = Math.round(wallpaper.getHeight() * 1.125f); - int spaceA = (wallpaper.getWidth() - goldenWidth) / 2; - int spaceB = (goldenWidth - Math.round(dh / scale)) / 2; - - bitmap = Bitmap.createBitmap(wallpaper, spaceA, 0, goldenWidth, - wallpaper.getHeight()); - int left = spaceB + Math.round(xOffset / scale); - bitmap = Bitmap.createBitmap(bitmap, left, 0, Math.round(dw / scale), - Math.round(dh / scale)); - } - - } else if (wallpaper.getWidth() < dw) { - // need to scale it up horizontally - - if (wallpaper.getHeight() > wallpaper.getWidth()) { - // handle portrait wallpaper - return wallpaper; - - } else { - // handle landscape wallpaper - float diff = wallpaper.getHeight() - wallpaper.getWidth(); - int diffhalf = Math.round(diff / 2); - - if (diffhalf < 0) { - return wallpaper; - } - - bitmap = Bitmap.createBitmap( - wallpaper, diffhalf, 0, - wallpaper.getWidth(), wallpaper.getWidth()); - - // blow it up - bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledWidth, true); - - bitmap = Bitmap.createBitmap(bitmap, 0, 0, dw, dh); - } - - } else { - // sometimes the wallpaper manager gives incorrect offsets, - // and adds like 200 pixels randomly. If it's bigger than we can handle, calculate - // our own :) - if (yOffset + dh > wallpaper.getHeight()) { - yOffset = (wallpaper.getHeight() - dh) / 2; - } - if (xOffset + dw > wallpaper.getWidth()) { - yOffset = (wallpaper.getWidth() - dw) / 2; - } - bitmap = Bitmap.createBitmap(wallpaper, xOffset, yOffset, dw, dh); - } - } catch (IllegalArgumentException e) { - // Cropping/resizing failed so return the original - bitmap = wallpaper; - } - return bitmap; - } - - public static boolean isRecentTaskHome(Context context) { - final ActivityManager am = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - - final List recentTasks = am.getRecentTasks( - 2, ActivityManager.RECENT_IGNORE_UNAVAILABLE); - if (recentTasks.size() > 1) { - ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(1); - - Intent intent = new Intent(recentInfo.baseIntent); - if (recentInfo.origActivity != null) { - intent.setComponent(recentInfo.origActivity); - } - - // Now check if this recent task is a launcher - if (isCurrentHomeActivity(context, intent.getComponent())) { - return true; - } - } - return false; - } - - public static boolean isRecentTaskThemeStore(Context context) { - final ActivityManager am = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - - final List recentTasks = am.getRecentTasks( - 2, ActivityManager.RECENT_IGNORE_UNAVAILABLE); - if (recentTasks.size() > 0) { - ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); - - Intent intent = new Intent(recentInfo.baseIntent); - if (recentInfo.origActivity != null) { - intent.setComponent(recentInfo.origActivity); - } - - if (intent.getComponent() - .getPackageName().equals(ChooserActivity.THEME_STORE_PACKAGE)) { - return true; - } - } - return false; - } - - - public static String getTopTaskPackageName(Context context) { - final ActivityManager am = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - final List recentTasks = am.getRecentTasks(1, 0); - if (recentTasks.size() > 0) { - ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); - if (recentInfo.origActivity != null) { - return recentInfo.origActivity.getPackageName(); - } - if (recentInfo.baseIntent != null) { - return recentInfo.baseIntent.getComponent().getPackageName(); - } - } - return null; - } - - public static boolean hasPerAppThemesApplied(Context context) { - final Configuration config = context.getResources().getConfiguration(); - final ThemeConfig themeConfig = config != null ? config.themeConfig : null; - if (themeConfig != null) { - Map themes = themeConfig.getAppThemes(); - for (String appPkgName : themes.keySet()) { - if (ThemeUtils.isPerAppThemeComponent(appPkgName)) { - return true; - } - } - } - return false; - } - - /** - * Method to identify if a theme explicitly overlays a particular app. Explicit is defined - * as having files in overlays/appPkgName/ - * @param context - * @param appPkgNane - * @param themePkgName - * @return - */ - public static boolean themeHasOverlayForApp(Context context, String appPkgNane, - String themePkgName) { - boolean hasExplicitOverlay = false; - if (ThemeConfig.SYSTEM_DEFAULT.equals(themePkgName)) { - hasExplicitOverlay = true; - } else { - try { - Context themeContext = context.createPackageContext(themePkgName, 0); - if (themeContext != null) { - AssetManager assets = themeContext.getAssets(); - String[] files = assets.list(OVERLAY_BASE_PATH + appPkgNane); - if (files != null && files.length > 0) hasExplicitOverlay = true; - } - } catch (Exception e) { - // don't care, we'll return false and let the caller handle things - } - } - return hasExplicitOverlay; - } - - private static boolean isCurrentHomeActivity(Context context, - ComponentName component) { - final PackageManager pm = context.getPackageManager(); - ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) - .resolveActivityInfo(pm, 0); - - return homeInfo != null - && homeInfo.packageName.equals(component.getPackageName()) - && homeInfo.name.equals(component.getClassName()); - } - - /** - * Returns the resource-IDs for all attributes specified in the given - * -resource tag as an int array. - * stackoverflow.com/questions/13816596/accessing-declare-styleable-resources-programatically - * - * @param name - * @return - */ - public static final int[] getResourceDeclareStyleableIntArray(String pkgName, String name) { - try { - //use reflection to access the resource class - Field[] fields2 = - Class.forName(pkgName + ".R$styleable").getFields(); - - //browse all fields - for (Field f : fields2) { - //pick matching field - if (f.getName().equals(name)) { - //return as int array - int[] ret = (int[])f.get(null); - return ret; - } - } - } - catch (Throwable t) { - } - - return null; - } - - /** - * Retrieves the list of dangerous permissions not granted to the supplied package. This method - * is not capable of identifying if a given permission was previously revoked by the user or - * if the user decided not to be asked again. - * - * @param context - * @param pkgName - * @return Returns an array of Strings with the name of the permissions. An empty array will - * be returned if all dangerous permissions have been already granted. - */ - public static String[] getDangerousPermissionsNotGranted(Context context, String pkgName) - throws InvalidParameterException { - LinkedList permissionsNotGranted = new LinkedList(); - PackageInfo pkgInfo = null; - PackageManager pm = context.getPackageManager(); - try { - pkgInfo = pm.getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS); - } catch (PackageManager.NameNotFoundException e) { - throw new InvalidParameterException("Package " + pkgName + " not found"); - } - - String[] requestedPermissions = pkgInfo.requestedPermissions; - int[] requestedPermissionsFlags = pkgInfo.requestedPermissionsFlags; - - for (int indx = 0; indx < requestedPermissions.length; indx++) { - if ((requestedPermissionsFlags[indx] & PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { - try { - PermissionInfo pi = pm.getPermissionInfo(requestedPermissions[indx],0); - - if (pi.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { - permissionsNotGranted.add(requestedPermissions[indx]); - if (DEBUG) { - Log.d(TAG, "Permission " + requestedPermissions[indx] + "not granted"); - } - } - } catch(PackageManager.NameNotFoundException e) { - //If package manager doesn't know of the permission we just continue since - //this permission won't end up in the list - } - } - } - return permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]); - } - - /** - * Builds a ComponentName to identify the activity associated with the - * KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION category in the given package - * @param context - * @param packageName - * @return Returns the ComponentName or null if no activity was found. - */ - private static ComponentName getPermissionGranterComponentName(Context context, - String packageName) { - Intent rIntent = new Intent() - .setPackage(packageName) - .addCategory(KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION); - - List resolveInfo = context.getPackageManager(). - queryIntentActivities(rIntent, PackageManager.GET_RESOLVED_FILTER); - - if (resolveInfo.size() >= 1) { - if (DEBUG) { - if (resolveInfo.size() >= 2) { - Log.w(TAG, "Got " + resolveInfo.size() + " resolvers! Defaulting to " - + resolveInfo.get(0).activityInfo.name); - } - } - } - return (resolveInfo.size() >=1 ) ? - new ComponentName(packageName, resolveInfo.get(0).activityInfo.name) : - null; - } - - /** - * Builds an intent used to request the user to grant or revoke the supplied permissions. - * The intent will set the KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION - * category and an extra containing the list of permissions identified by - * KeyguardExternalView.EXTRA_PERMISSION_LIST - * @param context - * @param packageName - * @param permissionList - * @return Returns the intent if an activity associated with - * KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION category was found. Otherwise, it - * returns null - */ - public static Intent buildPermissionGrantRequestIntent(Context context, String packageName, - String[] permissionList) { - ComponentName componentName = getPermissionGranterComponentName(context, packageName); - if (componentName == null) return null; - - Intent permissionIntent = new Intent() - .setComponent(componentName) - .addCategory(KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION) - .setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|Intent.FLAG_ACTIVITY_NEW_TASK) - .putExtra(KeyguardExternalView.EXTRA_PERMISSION_LIST, permissionList); - return permissionIntent; - } - - public static String getDefaultThemePackageName(Context context) { - final String defaultThemePkg = CMSettings.Secure.getString(context.getContentResolver(), - CMSettings.Secure.DEFAULT_THEME_PACKAGE); - if (!TextUtils.isEmpty(defaultThemePkg)) { - PackageManager pm = context.getPackageManager(); - try { - if (pm.getPackageInfo(defaultThemePkg, 0) != null) { - return defaultThemePkg; - } - } catch (PackageManager.NameNotFoundException e) { - // doesn't exist so system will be default - Log.w(TAG, "Default theme " + defaultThemePkg + " not found", e); - } - } - - return SYSTEM_DEFAULT; - } -} diff --git a/src/com/cyngn/theme/util/WallpaperUtils.java b/src/com/cyngn/theme/util/WallpaperUtils.java deleted file mode 100644 index 7f7536c..0000000 --- a/src/com/cyngn/theme/util/WallpaperUtils.java +++ /dev/null @@ -1,426 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.util; - -import android.app.WallpaperManager; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Canvas; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.RectF; -import android.net.Uri; -import android.os.AsyncTask; -import android.util.Log; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; - -public class WallpaperUtils { - private static final String TAG = WallpaperUtils.class.getSimpleName(); - private static final int DEFAULT_COMPRESS_QUALITY = 90; - - /** - * createThumbnail from WallpaperCropActivity in f/b/packages/WallpaperCropper, renamed - * tp createPreview. - */ - public static Bitmap createPreview(Point size, Context context, Uri uri, byte[] imageBytes, - Resources res, int resId, int rotation, boolean leftAligned) { - int width = size.x; - int height = size.y; - - BitmapCropTask cropTask; - if (uri != null) { - cropTask = new BitmapCropTask( - context, uri, null, rotation, width, height, false, true, null); - } else if (imageBytes != null) { - cropTask = new BitmapCropTask( - imageBytes, null, rotation, width, height, false, true, null); - } else { - cropTask = new BitmapCropTask( - context, res, resId, null, rotation, width, height, false, true, null); - } - Point bounds = cropTask.getImageBounds(); - if (bounds == null || bounds.x == 0 || bounds.y == 0) { - return null; - } - - Matrix rotateMatrix = new Matrix(); - rotateMatrix.setRotate(rotation); - float[] rotatedBounds = new float[] { bounds.x, bounds.y }; - rotateMatrix.mapPoints(rotatedBounds); - rotatedBounds[0] = Math.abs(rotatedBounds[0]); - rotatedBounds[1] = Math.abs(rotatedBounds[1]); - - RectF cropRect = getMaxCropRect( - (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned); - cropTask.setCropBounds(cropRect); - - if (cropTask.cropBitmap()) { - return cropTask.getCroppedBitmap(); - } else { - return null; - } - } - - /** - * getMaxCropRect from WallpaperCropActivity in f/b/packages/WallpaperCropper - */ - protected static RectF getMaxCropRect( - int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) { - RectF cropRect = new RectF(); - // Get a crop rect that will fit this - if (inWidth / (float) inHeight > outWidth / (float) outHeight) { - cropRect.top = 0; - cropRect.bottom = inHeight; - cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2; - cropRect.right = inWidth - cropRect.left; - if (leftAligned) { - cropRect.right -= cropRect.left; - cropRect.left = 0; - } - } else { - cropRect.left = 0; - cropRect.right = inWidth; - cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2; - cropRect.bottom = inHeight - cropRect.top; - } - return cropRect; - } - - /** - * convertExtensionToCompressFormat from WallpaperCropActivity in f/b/packages/WallpaperCropper - */ - protected static Bitmap.CompressFormat convertExtensionToCompressFormat(String extension) { - return extension.equals("png") ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG; - } - - /** - * getFileExtension from WallpaperCropActivity in f/b/packages/WallpaperCropper - */ - protected static String getFileExtension(String requestFormat) { - String outputFormat = (requestFormat == null) - ? "jpg" - : requestFormat; - outputFormat = outputFormat.toLowerCase(); - return (outputFormat.equals("png") || outputFormat.equals("gif")) - ? "png" // We don't support gif compression. - : "jpg"; - } - - /** - * BitmapCropTask from WallpaperCropActivity in f/b/packages/WallpaperCropper - */ - protected static class BitmapCropTask extends AsyncTask { - Uri mInUri = null; - Context mContext; - String mInFilePath; - byte[] mInImageBytes; - int mInResId = 0; - InputStream mInStream; - RectF mCropBounds = null; - int mOutWidth, mOutHeight; - int mRotation; - String mOutputFormat = "jpg"; // for now - boolean mSetWallpaper; - boolean mSaveCroppedBitmap; - Bitmap mCroppedBitmap; - Runnable mOnEndRunnable; - Resources mResources; - OnBitmapCroppedHandler mOnBitmapCroppedHandler; - boolean mNoCrop; - boolean mImageFromAsset; - - public BitmapCropTask(byte[] imageBytes, - RectF cropBounds, int rotation, int outWidth, int outHeight, - boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { - mInImageBytes = imageBytes; - init(cropBounds, rotation, - outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); - } - - public BitmapCropTask(Context c, Uri inUri, - RectF cropBounds, int rotation, int outWidth, int outHeight, - boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { - mContext = c; - mInUri = inUri; - init(cropBounds, rotation, - outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); - } - - public BitmapCropTask(Context c, Resources res, int inResId, - RectF cropBounds, int rotation, int outWidth, int outHeight, - boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { - mContext = c; - mInResId = inResId; - mResources = res; - init(cropBounds, rotation, - outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); - } - - private void init(RectF cropBounds, int rotation, int outWidth, int outHeight, - boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { - mCropBounds = cropBounds; - mRotation = rotation; - mOutWidth = outWidth; - mOutHeight = outHeight; - mSetWallpaper = setWallpaper; - mSaveCroppedBitmap = saveCroppedBitmap; - mOnEndRunnable = onEndRunnable; - } - - // Helper to setup input stream - private void regenerateInputStream() { - if (mInUri == null && mInResId == 0 && mInFilePath == null && - mInImageBytes == null && !mImageFromAsset) { - Log.w(TAG, "cannot read original file, no input URI, resource ID, or " + - "image byte array given"); - } else { - Utils.closeQuiet(mInStream); - try { - if (mInUri != null) { - mInStream = new BufferedInputStream( - mContext.getContentResolver().openInputStream(mInUri)); - } else if (mInFilePath != null) { - mInStream = mContext.openFileInput(mInFilePath); - } else if (mInImageBytes != null) { - mInStream = new BufferedInputStream( - new ByteArrayInputStream(mInImageBytes)); - } else { - mInStream = new BufferedInputStream( - mResources.openRawResource(mInResId)); - } - } catch (FileNotFoundException e) { - Log.w(TAG, "cannot read file: " + mInUri.toString(), e); - } - } - } - - public Point getImageBounds() { - regenerateInputStream(); - if (mInStream != null) { - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(mInStream, null, options); - if (options.outWidth != 0 && options.outHeight != 0) { - return new Point(options.outWidth, options.outHeight); - } - } - return null; - } - - public void setCropBounds(RectF cropBounds) { - mCropBounds = cropBounds; - } - - public Bitmap getCroppedBitmap() { - return mCroppedBitmap; - } - public boolean cropBitmap() { - boolean failure = false; - - regenerateInputStream(); - - WallpaperManager wallpaperManager = null; - if (mSetWallpaper) { - wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); - } - if (mSetWallpaper && mNoCrop && mInStream != null) { - try { - wallpaperManager.setStream(mInStream); - } catch (IOException e) { - Log.w(TAG, "cannot write stream to wallpaper", e); - failure = true; - } - return !failure; - } - if (mInStream != null) { - // Find crop bounds (scaled to original image size) - Rect roundedTrueCrop = new Rect(); - Matrix rotateMatrix = new Matrix(); - Matrix inverseRotateMatrix = new Matrix(); - if (mRotation > 0) { - rotateMatrix.setRotate(mRotation); - inverseRotateMatrix.setRotate(-mRotation); - - mCropBounds.roundOut(roundedTrueCrop); - mCropBounds = new RectF(roundedTrueCrop); - - Point bounds = getImageBounds(); - - float[] rotatedBounds = new float[] { bounds.x, bounds.y }; - rotateMatrix.mapPoints(rotatedBounds); - rotatedBounds[0] = Math.abs(rotatedBounds[0]); - rotatedBounds[1] = Math.abs(rotatedBounds[1]); - - mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2); - inverseRotateMatrix.mapRect(mCropBounds); - mCropBounds.offset(bounds.x/2, bounds.y/2); - - regenerateInputStream(); - } - - mCropBounds.roundOut(roundedTrueCrop); - - if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { - Log.w(TAG, "crop has bad values for full size image"); - failure = true; - return false; - } - - // See how much we're reducing the size of the image - int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth, - roundedTrueCrop.height() / mOutHeight); - - // Attempt to open a region decoder - BitmapRegionDecoder decoder = null; - try { - decoder = BitmapRegionDecoder.newInstance(mInStream, true); - } catch (IOException e) { - Log.w(TAG, "cannot open region decoder for file: " + mInUri.toString(), e); - } - - Bitmap crop = null; - if (decoder != null) { - // Do region decoding to get crop bitmap - BitmapFactory.Options options = new BitmapFactory.Options(); - if (scaleDownSampleSize > 1) { - options.inSampleSize = scaleDownSampleSize; - } - crop = decoder.decodeRegion(roundedTrueCrop, options); - decoder.recycle(); - } - - if (crop == null) { - // BitmapRegionDecoder has failed, try to crop in-memory - regenerateInputStream(); - Bitmap fullSize = null; - if (mInStream != null) { - BitmapFactory.Options options = new BitmapFactory.Options(); - if (scaleDownSampleSize > 1) { - options.inSampleSize = scaleDownSampleSize; - } - fullSize = BitmapFactory.decodeStream(mInStream, null, options); - } - if (fullSize != null) { - mCropBounds.left /= scaleDownSampleSize; - mCropBounds.top /= scaleDownSampleSize; - mCropBounds.bottom /= scaleDownSampleSize; - mCropBounds.right /= scaleDownSampleSize; - mCropBounds.roundOut(roundedTrueCrop); - - crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, - roundedTrueCrop.top, roundedTrueCrop.width(), - roundedTrueCrop.height()); - } - } - - if (crop == null) { - Log.w(TAG, "cannot decode file: " + mInUri.toString()); - failure = true; - return false; - } - if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) { - float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() }; - rotateMatrix.mapPoints(dimsAfter); - dimsAfter[0] = Math.abs(dimsAfter[0]); - dimsAfter[1] = Math.abs(dimsAfter[1]); - - if (!(mOutWidth > 0 && mOutHeight > 0)) { - mOutWidth = Math.round(dimsAfter[0]); - mOutHeight = Math.round(dimsAfter[1]); - } - - RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]); - RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight); - - Matrix m = new Matrix(); - if (mRotation == 0) { - m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); - } else { - Matrix m1 = new Matrix(); - m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f); - Matrix m2 = new Matrix(); - m2.setRotate(mRotation); - Matrix m3 = new Matrix(); - m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f); - Matrix m4 = new Matrix(); - m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); - - Matrix c1 = new Matrix(); - c1.setConcat(m2, m1); - Matrix c2 = new Matrix(); - c2.setConcat(m4, m3); - m.setConcat(c2, c1); - } - - Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), - (int) returnRect.height(), Bitmap.Config.ARGB_8888); - if (tmp != null) { - Canvas c = new Canvas(tmp); - Paint p = new Paint(); - p.setFilterBitmap(true); - c.drawBitmap(crop, m, p); - crop = tmp; - } - } - - if (mSaveCroppedBitmap) { - mCroppedBitmap = crop; - } - - // Get output compression format - Bitmap.CompressFormat cf = - convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); - - // Compress to byte array - ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); - if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { - // If we need to set to the wallpaper, set it - if (mSetWallpaper && wallpaperManager != null) { - try { - byte[] outByteArray = tmpOut.toByteArray(); - wallpaperManager.setStream(new ByteArrayInputStream(outByteArray)); - if (mOnBitmapCroppedHandler != null) { - mOnBitmapCroppedHandler.onBitmapCropped(outByteArray); - } - } catch (IOException e) { - Log.w(TAG, "cannot write stream to wallpaper", e); - failure = true; - } - } - } else { - Log.w(TAG, "cannot compress bitmap"); - failure = true; - } - } - return !failure; // True if any of the operations failed - } - - @Override - protected Boolean doInBackground(Void... params) { - return cropBitmap(); - } - - @Override - protected void onPostExecute(Boolean result) { - if (mOnEndRunnable != null) { - mOnEndRunnable.run(); - } - } - - public interface OnBitmapCroppedHandler { - public void onBitmapCropped(byte[] imageBytes); - } - } -} diff --git a/src/com/cyngn/theme/widget/AutoSnapHorizontalScrollView.java b/src/com/cyngn/theme/widget/AutoSnapHorizontalScrollView.java deleted file mode 100644 index e034357..0000000 --- a/src/com/cyngn/theme/widget/AutoSnapHorizontalScrollView.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.graphics.Rect; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.HorizontalScrollView; -import android.widget.LinearLayout; - -public class AutoSnapHorizontalScrollView extends HorizontalScrollView { - private static final int SNAP_ON_UP_DELAY = 250; - - private int mScrollPositionOnUp; - - enum EventStates { - SCROLLING, - FLING - } - - private EventStates mSystemState = EventStates.SCROLLING; - - public AutoSnapHorizontalScrollView(Context context) { - super(context); - } - - public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - Runnable mSnapRunnable = new Runnable(){ - @Override - public void run() { - snapItems(); - mSystemState = EventStates.SCROLLING; - } - }; - - /** - * Added runnable for snapping on an item when the user lifts up their finger and - * there is no scrolling taking place (i.e. no flinging) - */ - Runnable mSnapOnUpRunnable = new Runnable(){ - @Override - public void run() { - int scrollX = getScrollX(); - if (scrollX != mScrollPositionOnUp) { - mScrollPositionOnUp = scrollX; - postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); - } else { - snapItems(); - mSystemState = EventStates.SCROLLING; - } - } - }; - - @Override - public boolean onTouchEvent(MotionEvent ev) { - int action = ev.getAction(); - if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { - mSystemState = EventStates.FLING; - removeCallbacks(mSnapRunnable); - mScrollPositionOnUp = getScrollX(); - postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); - } else if (action == MotionEvent.ACTION_DOWN) { - mSystemState = EventStates.SCROLLING; - removeCallbacks(mSnapRunnable); - removeCallbacks(mSnapOnUpRunnable); - } - return super.onTouchEvent(ev); - } - - private void snapItems() { - Rect parentBounds = new Rect(); - getDrawingRect(parentBounds); - Rect childBounds = new Rect(); - ViewGroup parent = (ViewGroup) getChildAt(0); - for (int i = 0; i < parent.getChildCount(); i++) { - View view = parent.getChildAt(i); - view.getHitRect(childBounds); - if (childBounds.right >= parentBounds.left && childBounds.left <= parentBounds.left) { - // First partially visible child - if ((childBounds.right - parentBounds.left) >= - (parentBounds.left - childBounds.left)) { - smoothScrollTo(Math.abs(childBounds.left), 0); - } else { - /** - * Added code to take into account dividers so that we do not see - * one on the edge of the screen when items snap in place. - */ - int dividerWidth = 0; - if (parent instanceof LinearLayout) { - dividerWidth = ((LinearLayout) parent).getDividerWidth(); - } - smoothScrollTo(Math.abs(childBounds.right) + dividerWidth, 0); - } - break; - } - } - } - - // Overwrite measureChildX as we want our child to be able to tell the - // parents width but do not impose any limits (AT_MOST; AT_MAX) - @Override - protected void measureChild(View child, int parentWidthMeasureSpec, - int parentHeightMeasureSpec) { - ViewGroup.LayoutParams lp = child.getLayoutParams(); - - int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop - + mPaddingBottom, lp.height); - - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parentWidthMeasureSpec) - (mPaddingLeft + mPaddingRight), - MeasureSpec.UNSPECIFIED); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - - @Override - protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, - int parentHeightMeasureSpec, int heightUsed) { - MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); - - int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, - mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin - + heightUsed, lp.height); - - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - MeasureSpec.getSize(parentWidthMeasureSpec) - - (mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin - + widthUsed), MeasureSpec.UNSPECIFIED); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - if (mSystemState == EventStates.SCROLLING) { - return; - } - if (Math.abs(l - oldl) <= 1 && mSystemState == EventStates.FLING) { - removeCallbacks(mSnapRunnable); - postDelayed(mSnapRunnable, 100); - } - } -} diff --git a/src/com/cyngn/theme/widget/BootAniImageView.java b/src/com/cyngn/theme/widget/BootAniImageView.java deleted file mode 100644 index 44363d6..0000000 --- a/src/com/cyngn/theme/widget/BootAniImageView.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.Log; -import android.widget.ImageView; -import libcore.io.IoUtils; -import com.cyngn.theme.util.BootAnimationHelper; - -import java.io.IOException; -import java.util.List; -import java.util.zip.ZipFile; - -public class BootAniImageView extends ImageView { - private static final String TAG = BootAniImageView.class.getName(); - - private static final boolean DEBUG = false; - - private static final int MAX_BUFFERS = 2; - - private Bitmap[] mBuffers = new Bitmap[MAX_BUFFERS]; - private int mReadBufferIndex = 0; - private int mWriteBufferIndex = 0; - private ZipFile mBootAniZip; - - private List mAnimationParts; - private int mCurrentPart; - private int mCurrentFrame; - private int mCurrentPartPlayCount; - private int mFrameDuration; - - private boolean mActive = false; - - public BootAniImageView(Context context) { - this(context, null); - } - - public BootAniImageView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public BootAniImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public void setVisibility(int visibility) { - super.setVisibility(visibility); - if (visibility == VISIBLE) { - if (mBootAniZip != null) start(); - } else { - stop(); - } - } - - @Override - protected void onDraw(Canvas canvas) { - // In case we end up in the mid dle of onDraw while the buffers are being recycled - // we catch the exception and just let frame not be rendered. - try { - super.onDraw(canvas); - } catch (RuntimeException e) { - Log.e(TAG, "Unable to draw boot animation frame."); - } - } - - public synchronized boolean setBootAnimation(ZipFile bootAni) { - if (bootAni == null) { - Log.w(TAG, "Boot animation ZipFile is null."); - return false; - } - // make sure we are stopped first - stop(); - - if (mBootAniZip != null) { - IoUtils.closeQuietly(mBootAniZip); - // This boot animation may be a different size than the previous - // one so clear out the buffers so they can be recreated with - // the correct size. - for (int i = 0; i < MAX_BUFFERS; i++) { - if (mBuffers[i] != null) { - mBuffers[i].recycle(); - mBuffers[i] = null; - } - } - } - mBootAniZip = bootAni; - - try { - mAnimationParts = BootAnimationHelper.parseAnimation(mBootAniZip); - } catch (Exception e) { - // "Gotta catch 'em all" - Log.e(TAG, "Unable to set boot animation", e); - mAnimationParts = null; - mBootAniZip = null; - return false; - } - - if (mAnimationParts == null || mAnimationParts.size() == 0) { - return false; - } - - final BootAnimationHelper.AnimationPart part = mAnimationParts.get(0); - mCurrentPart = 0; - mCurrentPartPlayCount = part.playCount; - mFrameDuration = part.frameRateMillis; - mWriteBufferIndex = mReadBufferIndex = 0; - mCurrentFrame = 0; - - getNextFrame(); - - return true; - } - - private void getNextFrame() { - if (mAnimationParts == null || mAnimationParts.size() == 0) return; - - BitmapFactory.Options opts = new BitmapFactory.Options(); - opts.inBitmap = mBuffers[mWriteBufferIndex]; - opts.inPreferredConfig = Bitmap.Config.RGB_565; - opts.inMutable = true; - final BootAnimationHelper.AnimationPart part = mAnimationParts.get(mCurrentPart); - try { - mBuffers[mWriteBufferIndex] = - BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( - part.frames.get(mCurrentFrame++))), null, opts); - } catch (IllegalArgumentException iae) { - // In case we're here because the bitmap could not be re-used, try creating a new one - opts.inBitmap = null; - try { - if (DEBUG) { - Log.d(TAG, "Trying to load frame without reusing existing bitmap", iae); - } - if (mBuffers[mWriteBufferIndex] != null) { - // clean up our old bitmap - mBuffers[mWriteBufferIndex].recycle(); - mBuffers[mWriteBufferIndex] = null; - } - mBuffers[mWriteBufferIndex] = - BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( - part.frames.get(mCurrentFrame++))), null, opts); - } catch (Exception e) { - // Still failling? Let's log it and carry on. - Log.w(TAG, "Unable to get next frame", e); - } - } catch (IOException e) { - Log.w(TAG, "Unable to get next frame", e); - } - mWriteBufferIndex = (mWriteBufferIndex + 1) % MAX_BUFFERS; - if (mCurrentFrame >= part.frames.size()) { - if (mCurrentPartPlayCount > 0) { - if (--mCurrentPartPlayCount == 0) { - mCurrentPart++; - if (mCurrentPart >= mAnimationParts.size()) mCurrentPart = 0; - mCurrentFrame = 0; - mCurrentPartPlayCount = mAnimationParts.get(mCurrentPart).playCount; - } else { - mCurrentFrame = 0; - } - } else { - mCurrentFrame = 0; - } - } - } - - public void start() { - if (mAnimationParts == null) return; - - mActive = true; - post(mUpdateAnimationRunnable); - } - - public void stop() { - mActive = false; - removeCallbacks(mUpdateImageRunnable); - removeCallbacks(mUpdateAnimationRunnable); - } - - private Runnable mUpdateAnimationRunnable = new Runnable() { - @Override - public void run() { - if (!mActive) return; - BootAniImageView.this.postDelayed(mUpdateAnimationRunnable, mFrameDuration); - if (!isOffScreen()) { - BootAniImageView.this.post(mUpdateImageRunnable); - mReadBufferIndex = (mReadBufferIndex + 1) % MAX_BUFFERS; - getNextFrame(); - } - } - }; - - private Runnable mUpdateImageRunnable = new Runnable() { - @Override - public void run() { - setImageBitmap(mBuffers[mReadBufferIndex]); - } - }; - - private boolean isOffScreen() { - int[] pos = new int[2]; - getLocationOnScreen(pos); - return pos[1] >= mContext.getResources().getDisplayMetrics().heightPixels; - } -} diff --git a/src/com/cyngn/theme/widget/ConfirmCancelOverlay.java b/src/com/cyngn/theme/widget/ConfirmCancelOverlay.java deleted file mode 100644 index 54fffa1..0000000 --- a/src/com/cyngn/theme/widget/ConfirmCancelOverlay.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.TextView; -import com.cyngn.theme.chooser.R; - -public class ConfirmCancelOverlay extends FrameLayout { - - private View mAcceptButton; - private View mCancelButton; - private TextView mTitle; - - private OnOverlayDismissedListener mListener; - - public ConfirmCancelOverlay(Context context) { - this(context, null); - } - - public ConfirmCancelOverlay(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ConfirmCancelOverlay(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mAcceptButton = findViewById(R.id.accept); - mCancelButton = findViewById(R.id.cancel); - mTitle = (TextView) findViewById(R.id.overlay_title); - - mAcceptButton.setOnClickListener(mClickListener); - mCancelButton.setOnClickListener(mClickListener); - } - - public void setTitle(CharSequence title) { - mTitle.setText(title); - } - - public void setTitle(int resId) { - mTitle.setText(resId); - } - - public void setOnOverlayDismissedListener(OnOverlayDismissedListener listener) { - mListener = listener; - } - - public void dismiss() { - if (mListener != null) { - mListener.onDismissed(false); - } - } - - private OnClickListener mClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - if (mListener != null) { - mListener.onDismissed(v == mAcceptButton); - } - } - }; - - public interface OnOverlayDismissedListener { - public void onDismissed(boolean accepted); - } -} diff --git a/src/com/cyngn/theme/widget/FittedTextView.java b/src/com/cyngn/theme/widget/FittedTextView.java deleted file mode 100644 index 2406a59..0000000 --- a/src/com/cyngn/theme/widget/FittedTextView.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.graphics.Paint; -import android.text.method.TransformationMethod; -import android.text.method.AllCapsTransformationMethod; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.widget.TextView; - -/** - * Change the font size to match the measured - * textview size by width - * - */ -public class FittedTextView extends TextView { - private Paint mPaint; - //If set to true, the text will be resized to fit the view. - private boolean mAutoFitText = true; - //Used to instruct whether the text should be expanded to fill out the view, even if the text - //fits without being resized - private boolean mAutoExpand = true; - - public FittedTextView(Context context) { - this(context, null); - } - - public FittedTextView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public FittedTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mPaint = new Paint(); - } - - protected void setAutoFitText(boolean autoFit) { - mAutoFitText = autoFit; - } - - protected boolean getAutoFitText() { - return mAutoFitText; - } - - protected void setAutoExpand(boolean autoExpand) { - mAutoExpand = autoExpand; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (!mAutoFitText) return; - - final float THRESHOLD = 0.5f; - final float TARGET_WIDTH = getMeasuredWidth(); - String text = getText().toString(); - TransformationMethod tm = getTransformationMethod(); - if (tm != null && tm instanceof AllCapsTransformationMethod) { - text = getText().toString().toUpperCase(); - } - mPaint.set(getPaint()); - - if (mPaint.measureText(text) <= TARGET_WIDTH && !mAutoExpand) return; - - float max = 200; - float min = 2; - while(max > min) { - float size = (max+min) / 2; - mPaint.setTextSize(size); - float measuredWidth = mPaint.measureText(text); - if (Math.abs(TARGET_WIDTH - measuredWidth) <= THRESHOLD) { - break; - } else if (measuredWidth > TARGET_WIDTH) { - max = size-1; - } else { - min = size+1; - } - } - this.setTextSize(TypedValue.COMPLEX_UNIT_PX, min-1); - } -} diff --git a/src/com/cyngn/theme/widget/LatoTextView.java b/src/com/cyngn/theme/widget/LatoTextView.java deleted file mode 100644 index 9ced870..0000000 --- a/src/com/cyngn/theme/widget/LatoTextView.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.content.res.AssetManager; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Typeface; -import android.util.AttributeSet; -import android.widget.TextView; -import com.cyngn.theme.chooser.R; -import com.cyngn.theme.util.Utils; - -import java.io.File; - -/** - * A custom TextView that always uses the Lato font - */ -public class LatoTextView extends FittedTextView { - private static final int NUM_TYPEFACE_PER_FAMILY = 4; - - private static final String FONT_ASSSET_DIR = "fonts"; - // Regular fonts - private static final String LATO_REGULAR_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-Regular.ttf"; - private static final String LATO_REGULAR_BOLD_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-RegBold.ttf"; - private static final String LATO_REGULAR_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-RegItalic.otf"; - private static final String LATO_REGULAR_BOLD_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-RegBoldItalic.ttf"; - // Condensed fonts - private static final String LATO_CONDENSED_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-Cond.ttf"; - private static final String LATO_CONDENSED_BOLD_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-CondBold.ttf"; - private static final String LATO_CONDENSED_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-CondItalic.ttf"; - private static final String LATO_CONDENSED_BOLD_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-CondBoldItalic.ttf"; - // Light fonts - private static final String LATO_LIGHT_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-Light.ttf"; - private static final String LATO_LIGHT_ITALIC_PATH = - FONT_ASSSET_DIR + File.separator + "Lato-LightItalic.ttf"; - - private static final String CONDENSED = "condensed"; - private static final String LIGHT = "light"; - - private static Typeface[] sLatoRegularTypeface; - private static Typeface[] sLatoCondensedTypeface; - private static Typeface[] sLatoLightTypeface; - private static final Object sLock = new Object(); - - // Retrieving these attributes is done via reflection so let's just do this once and share - // it amongst the other instances of LatoTextView - private static int[] sTextViewStyleAttributes; - - public LatoTextView(Context context) { - this(context, null); - } - - public LatoTextView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public LatoTextView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - synchronized (sLock) { - AssetManager assets = context.getAssets(); - if (sLatoRegularTypeface == null) { - sLatoRegularTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; - sLatoRegularTypeface[Typeface.NORMAL] = - Typeface.createFromAsset(assets, LATO_REGULAR_PATH); - sLatoRegularTypeface[Typeface.BOLD] = - Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_PATH); - sLatoRegularTypeface[Typeface.ITALIC] = - Typeface.createFromAsset(assets, LATO_REGULAR_ITALIC_PATH); - sLatoRegularTypeface[Typeface.BOLD_ITALIC] = - Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_ITALIC_PATH); - } - if (sLatoCondensedTypeface == null) { - sLatoCondensedTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; - sLatoCondensedTypeface[Typeface.NORMAL] = - Typeface.createFromAsset(assets, LATO_CONDENSED_PATH); - sLatoCondensedTypeface[Typeface.BOLD] = - Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_PATH); - sLatoCondensedTypeface[Typeface.ITALIC] = - Typeface.createFromAsset(assets, LATO_CONDENSED_ITALIC_PATH); - sLatoCondensedTypeface[Typeface.BOLD_ITALIC] = - Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_ITALIC_PATH); - } - if (sLatoLightTypeface == null) { - sLatoLightTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; - sLatoLightTypeface[Typeface.NORMAL] = - Typeface.createFromAsset(assets, LATO_LIGHT_PATH); - sLatoLightTypeface[Typeface.BOLD] = - sLatoRegularTypeface[Typeface.BOLD]; - sLatoLightTypeface[Typeface.ITALIC] = - Typeface.createFromAsset(assets, LATO_LIGHT_ITALIC_PATH); - sLatoLightTypeface[Typeface.BOLD_ITALIC] = - sLatoRegularTypeface[Typeface.BOLD_ITALIC]; - } - } - - final Resources.Theme theme = context.getTheme(); - if (sTextViewStyleAttributes == null) { - sTextViewStyleAttributes = - Utils.getResourceDeclareStyleableIntArray("com.android.internal", "TextView"); - } - - if (sTextViewStyleAttributes != null) { - TypedArray a = - theme.obtainStyledAttributes(attrs, sTextViewStyleAttributes, defStyle, 0); - String fontFamily = "sans-serif"; - int styleIndex = Typeface.NORMAL; - if (a != null) { - int n = a.getIndexCount(); - for (int i = 0; i < n; i++) { - int attr = a.getIndex(i); - - final Resources res = getResources(); - int attrFontFamily = - res.getIdentifier("TextView_fontFamily", "styleable", "android"); - int attrTextStyle = - res.getIdentifier("TextView_textStyle", "styleable", "android"); - if (attr == attrFontFamily) { - fontFamily = a.getString(attr); - } else if (attr == attrTextStyle) { - styleIndex = a.getInt(attr, styleIndex); - } - } - a.recycle(); - } - - setTypefaceFromAttrs(fontFamily, styleIndex); - setAutoExpand(false); - TypedArray styledAttrs = context.obtainStyledAttributes(attrs, - R.styleable.FittedTextView, 0, 0); - try { - //Although we extend FittedTextView, we don't want all instances to auto fit the - //text, so we check if autoFitText has been set in the attributes. Default to false - boolean fit = styledAttrs.getBoolean(R.styleable.FittedTextView_autoFitText, false); - setAutoFitText(fit); - } finally { - styledAttrs.recycle(); - } - } - } - - private void setTypefaceFromAttrs(String familyName, int styleIndex) { - Typeface tf = null; - if (familyName != null) { - Typeface[] typefaces = sLatoRegularTypeface; - if (familyName.contains(CONDENSED)) { - typefaces = sLatoCondensedTypeface; - } else if (familyName.contains(LIGHT)) { - typefaces = sLatoLightTypeface; - } - tf = typefaces[styleIndex]; - if (tf != null) { - setTypeface(tf); - return; - } - } - setTypeface(tf, styleIndex); - } -} diff --git a/src/com/cyngn/theme/widget/LockableScrollView.java b/src/com/cyngn/theme/widget/LockableScrollView.java deleted file mode 100644 index 88f484a..0000000 --- a/src/com/cyngn/theme/widget/LockableScrollView.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.widget.ScrollView; - -public class LockableScrollView extends ScrollView { - private boolean mScrollingEnabled = true; - - public LockableScrollView(Context context) { - this(context, null); - } - - public LockableScrollView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public LockableScrollView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public void setScrollingEnabled(boolean enabled) { - mScrollingEnabled = enabled; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - return mScrollingEnabled && super.onInterceptTouchEvent(ev); - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - return mScrollingEnabled && super.onTouchEvent(ev); - default: - return super.onTouchEvent(ev); - } - } - - @Override - public void setOverScrollMode(int mode) { - // Some themes can cause theme chooser to crash when creating the EdgeEffects for - // the scroll view. If an exception occurs we fallback to no overscroll - try { - super.setOverScrollMode(mode); - } catch (Exception e) { - super.setOverScrollMode(OVER_SCROLL_NEVER); - } - } -} diff --git a/src/com/cyngn/theme/widget/NavBarSpace.java b/src/com/cyngn/theme/widget/NavBarSpace.java deleted file mode 100644 index 993e9cf..0000000 --- a/src/com/cyngn/theme/widget/NavBarSpace.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2014 Cyanogen, Inc. - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import com.cyngn.theme.util.Utils; - -/** - * A simple view used to pad layouts so that content floats above the - * navigation bar. This is best used with transparent or translucent - * navigation bars where the content can go behind them. - */ -public class NavBarSpace extends View { - - public NavBarSpace(Context context) { - this(context, null); - } - - public NavBarSpace(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public NavBarSpace(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - if (!Utils.hasNavigationBar(getContext())) { - this.setVisibility(View.GONE); - } else { - this.setVisibility(View.VISIBLE); - } - } -} diff --git a/src/com/cyngn/theme/widget/ThemeTagLayout.java b/src/com/cyngn/theme/widget/ThemeTagLayout.java deleted file mode 100644 index 18635a4..0000000 --- a/src/com/cyngn/theme/widget/ThemeTagLayout.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2014 The Cyanogen, Inc - */ -package com.cyngn.theme.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import com.cyngn.theme.chooser.R; - -public class ThemeTagLayout extends LinearLayout { - private ImageView mAppliedTag; - private TextView mCustomizedTag; - private TextView mUpdatedTag; - private TextView mDefaultTag; - private TextView mLegacyTag; - - public ThemeTagLayout(Context context) { - this(context, null); - } - - public ThemeTagLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ThemeTagLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - LayoutInflater inflater = - (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mAppliedTag = (ImageView) inflater.inflate(R.layout.tag_applied, this, false); - mCustomizedTag = (TextView) inflater.inflate(R.layout.tag_customized, this, false); - mUpdatedTag = (TextView) inflater.inflate(R.layout.tag_updated, this, false); - mDefaultTag = (TextView) inflater.inflate(R.layout.tag_default, this, false); - mLegacyTag = (TextView) inflater.inflate(R.layout.tag_legacy, this, false); - } - - public void setAppliedTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_applied) != null) return; - addView(mAppliedTag, 0); - } else { - if (findViewById(R.id.tag_applied) == null) return; - removeView(mAppliedTag); - } - } - - public void setCustomizedTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_customized) != null) return; - final int childCount = getChildCount(); - if (childCount > 1) { - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child != mAppliedTag) { - addView(mCustomizedTag, i); - break; - } - } - } else { - addView(mCustomizedTag); - } - } else { - if (findViewById(R.id.tag_customized) == null) return; - removeView(mCustomizedTag); - } - } - - public boolean isCustomizedTagEnabled() { - return findViewById(R.id.tag_customized) != null; - } - - public void setUpdatedTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_updated) != null) return; - final int childCount = getChildCount(); - if (childCount > 2) { - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child != mAppliedTag && child != mCustomizedTag) { - addView(mUpdatedTag, i); - break; - } - } - } else { - addView(mUpdatedTag); - } - } else { - if (findViewById(R.id.tag_updated) == null) return; - removeView(mUpdatedTag); - } - } - - public boolean isUpdatedTagEnabled() { - return findViewById(R.id.tag_updated) != null; - } - - public void setDefaultTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_default) != null) return; - addView(mDefaultTag); - } else { - if (findViewById(R.id.tag_default) == null) return; - removeView(mDefaultTag); - } - } - - public void setLegacyTagEnabled(boolean enabled) { - if (enabled) { - if (findViewById(R.id.tag_legacy) != null) return; - addView(mLegacyTag); - } else { - if (findViewById(R.id.tag_legacy) == null) return; - removeView(mLegacyTag); - } - } -} diff --git a/src/com/viewpagerindicator/CirclePageIndicator.java b/src/com/viewpagerindicator/CirclePageIndicator.java index 81365af..19b031e 100644 --- a/src/com/viewpagerindicator/CirclePageIndicator.java +++ b/src/com/viewpagerindicator/CirclePageIndicator.java @@ -34,7 +34,7 @@ import android.view.View; import android.view.ViewConfiguration; import au.com.glassechidna.velocityviewpager.VelocityViewPager; -import com.cyngn.theme.chooser.R; +import org.cyanogenmod.theme.chooser.R; import static android.graphics.Paint.ANTI_ALIAS_FLAG; import static android.widget.LinearLayout.HORIZONTAL; diff --git a/src/org/cyanogenmod/theme/chooser/AppReceiver.java b/src/org/cyanogenmod/theme/chooser/AppReceiver.java new file mode 100644 index 0000000..0c3037e --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/AppReceiver.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.text.TextUtils; + +import org.cyanogenmod.theme.util.NotificationHelper; +import org.cyanogenmod.theme.util.PreferenceUtils; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.providers.ThemesContract; + +public class AppReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + Uri uri = intent.getData(); + String pkgName = uri != null ? uri.getSchemeSpecificPart() : null; + String action = intent.getAction(); + + if (cyanogenmod.content.Intent.ACTION_THEME_INSTALLED.equals(action)) { + if (!pkgName.equals(Utils.getDefaultThemePackageName(context))) { + NotificationHelper.postThemeInstalledNotification(context, pkgName); + } + } else if (cyanogenmod.content.Intent.ACTION_THEME_REMOVED.equals(action)) { + // remove updated status for this theme (if one exists) + PreferenceUtils.removeUpdatedTheme(context, pkgName); + + // If the theme being removed was the currently applied theme we need + // to update the applied base theme in preferences to the default theme. + String appliedBaseTheme = PreferenceUtils.getAppliedBaseTheme(context); + if (!TextUtils.isEmpty(appliedBaseTheme) && appliedBaseTheme.equals(pkgName)) { + PreferenceUtils.setAppliedBaseTheme(context, + Utils.getDefaultThemePackageName(context)); + } + NotificationHelper.cancelNotifications(context); + } else if (cyanogenmod.content.Intent.ACTION_THEME_UPDATED.equals(action)) { + try { + if (isTheme(context, pkgName)) { + PreferenceUtils.addUpdatedTheme(context, pkgName); + } + } catch (NameNotFoundException e) { + } + } + } + + private boolean isTheme(Context context, String pkgName) throws NameNotFoundException { + PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); + + return pi != null && pi.themeInfo != null; + } +} diff --git a/src/org/cyanogenmod/theme/chooser/BootReceiver.java b/src/org/cyanogenmod/theme/chooser/BootReceiver.java new file mode 100644 index 0000000..00a6723 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/BootReceiver.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +public class BootReceiver extends BroadcastReceiver { + private static final String CHOOSER_PKG_NAME = "org.cyanogenmod.theme.chooser"; + private static final String CHOOSER_ACTIVITY = "org.cyanogenmod.theme.chooser.ChooserLauncher"; + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + PackageManager pm = context.getPackageManager(); + if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { + try { + PackageInfo info = pm.getPackageInfo(ChooserActivity.THEME_STORE_PACKAGE, 0); + if (info != null) { + ComponentName cn = new ComponentName(CHOOSER_PKG_NAME, CHOOSER_ACTIVITY); + pm.setComponentEnabledSetting(cn, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + } + } catch (PackageManager.NameNotFoundException e) { + // no store so nothing to do. + } + + // now disable this receiver so we don't get called on future boots + ComponentName cn = new ComponentName(CHOOSER_PKG_NAME, + BootReceiver.class.getCanonicalName()); + pm.setComponentEnabledSetting(cn, + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP); + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/ChooserActivity.java b/src/org/cyanogenmod/theme/chooser/ChooserActivity.java new file mode 100644 index 0000000..081325d --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/ChooserActivity.java @@ -0,0 +1,1191 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.IPackageDeleteObserver; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.ColorMatrix; +import android.graphics.ColorMatrixColorFilter; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Handler; +import android.os.RemoteException; +import android.provider.Settings; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.renderscript.ScriptIntrinsicBlur; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.support.v4.view.ThemeViewPager; +import android.support.v4.view.ViewPager; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.MutableLong; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewPropertyAnimator; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.Toast; + +import org.cyanogenmod.theme.perapptheming.PerAppThemingWindow; +import org.cyanogenmod.theme.util.CursorLoaderHelper; +import org.cyanogenmod.theme.util.NotificationHelper; +import org.cyanogenmod.theme.util.PreferenceUtils; +import org.cyanogenmod.theme.util.TypefaceHelperCache; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.platform.Manifest; +import cyanogenmod.providers.ThemesContract; +import cyanogenmod.providers.ThemesContract.ThemesColumns; +import cyanogenmod.themes.ThemeManager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; +import static org.cyanogenmod.theme.chooser.ComponentSelector.DEFAULT_COMPONENT_ID; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_INSTALLED_THEMES; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_APPLIED; + +public class ChooserActivity extends FragmentActivity + implements LoaderManager.LoaderCallbacks { + public static final String THEME_STORE_PACKAGE = "com.cyngn.theme.tore"; + private static final String TAG = ChooserActivity.class.getSimpleName(); + + public static final String DEFAULT = ThemeConfig.SYSTEM_DEFAULT; + public static final String EXTRA_PKGNAME = "pkgName"; + public static final String EXTRA_COMPONENTS = "components"; + + private static final int OFFSCREEN_PAGE_LIMIT = 3; + + private static final String THEME_STORE_ACTIVITY = THEME_STORE_PACKAGE + ".ui.StoreActivity"; + private static final String ACTION_APPLY_THEME = "android.intent.action.APPLY_THEME"; + + private static final String TYPE_IMAGE = "image/*"; + + private static final String ACTION_PICK_LOCK_SCREEN_WALLPAPER = + "com.cyngn.intent.action.PICK_LOCK_SCREEN_WALLPAPER"; + + /** + * Request code for picking an external wallpaper + */ + public static final int REQUEST_PICK_WALLPAPER_IMAGE = 2; + /** + * Request code for picking an external lockscreen wallpaper + */ + public static final int REQUEST_PICK_LOCKSCREEN_IMAGE = 3; + + /** + * Request code for enabling system alert window permission + */ + private static final int REQUEST_SYSTEM_WINDOW_PERMISSION = 4; + + private static final long ANIMATE_CONTENT_IN_SCALE_DURATION = 500; + private static final long ANIMATE_CONTENT_IN_ALPHA_DURATION = 750; + private static final long ANIMATE_CONTENT_IN_BLUR_DURATION = 250; + private static final long ANIMATE_CONTENT_DELAY = 250; + private static final long ANIMATE_SHOP_THEMES_HIDE_DURATION = 250; + private static final long ANIMATE_SHOP_THEMES_SHOW_DURATION = 500; + private static final long FINISH_ANIMATION_DELAY = ThemeFragment.ANIMATE_DURATION + + ThemeFragment.ANIMATE_START_DELAY + 250; + + private static final long ANIMATE_CARDS_IN_DURATION = 250; + private static final long ANIMATE_SAVE_APPLY_LAYOUT_DURATION = 300; + private static final float ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR = 3; + private static final long ONCLICK_SAVE_APPLY_FINISH_ANIMATION_DELAY = 400; + + private PagerContainer mContainer; + private ThemeViewPager mPager; + + private ThemesAdapter mAdapter; + private boolean mExpanded = false; + private ComponentSelector mSelector; + private View mSaveApplyLayout; + private int mContainerYOffset = 0; + private TypefaceHelperCache mTypefaceHelperCache; + private boolean mIsAnimating; + private Handler mHandler; + private View mBottomActionsLayout; + + private String mSelectedTheme; + private String mAppliedBaseTheme; + private boolean mThemeChanging = false; + private boolean mAnimateContentIn = false; + private long mAnimateContentInDelay; + private String mThemeToApply; + private ArrayList mComponentsToApply; + + ImageView mCustomBackground; + + // Current system theme configuration as component -> pkgName + private Map mCurrentTheme = new HashMap(); + private MutableLong mCurrentWallpaperCmpntId = new MutableLong(DEFAULT_COMPONENT_ID); + + private boolean mIsPickingImage = false; + private boolean mRestartLoaderOnCollapse = false; + private boolean mActivityResuming = false; + private boolean mShowLockScreenWallpaper = false; + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + NotificationHijackingService.ensureEnabled(this); + + if (savedInstanceState == null) { + handleIntent(getIntent()); + } + + mContainer = (PagerContainer) findViewById(R.id.pager_container); + mPager = (ThemeViewPager) findViewById(R.id.viewpager); + + mPager.setOnClickListener(mPagerClickListener); + mAdapter = new ThemesAdapter(); + mPager.setAdapter(mAdapter); + + DisplayMetrics dm = getResources().getDisplayMetrics(); + int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, dm); + mPager.setPageMargin(-margin / 2); + mPager.setOffscreenPageLimit(OFFSCREEN_PAGE_LIMIT); + + mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { + public void onPageSelected(int position) { + } + + public void onPageScrolled(int position, + float positionOffset, + int positionOffsetPixels) { + } + + public void onPageScrollStateChanged(int state) { + } + }); + + mSelector = (ComponentSelector) findViewById(R.id.component_selector); + mSelector.setOnOpenCloseListener(mOpenCloseListener); + + mBottomActionsLayout = findViewById(R.id.bottom_actions_layout); + + mSaveApplyLayout = findViewById(R.id.save_apply_layout); + mSaveApplyLayout.findViewById(R.id.save_apply_button).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mIsAnimating) return; + hideSaveApplyButton(); + mContainer.setClickable(false); + final ThemeFragment f = getCurrentFragment(); + if (mSelector.isEnabled()) { + mSelector.hide(); + if (mContainerYOffset != 0) { + slideContentBack(-mContainerYOffset); + mContainerYOffset = 0; + } + if (f != null) f.fadeInCards(); + if (mShowLockScreenWallpaper) { + mShowLockScreenWallpaper = false; + mSelector.resetComponentType(); + } + } + + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + collapse(true); + } + }, ONCLICK_SAVE_APPLY_FINISH_ANIMATION_DELAY); + } + }); + + mBottomActionsLayout.findViewById(R.id.shop_themes) + .setOnClickListener(mOnShopThemesClicked); + + mTypefaceHelperCache = TypefaceHelperCache.getInstance(); + mHandler = new Handler(); + mCustomBackground = (ImageView) findViewById(R.id.custom_bg); + mAnimateContentIn = true; + mAnimateContentInDelay = 0; + + mBottomActionsLayout.findViewById(R.id.per_app_theming).setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (Settings.canDrawOverlays(ChooserActivity.this)) { + launchAppThemer(); + } else { + requestSystemWindowPermission(); + } + } + }); + + if (PreferenceUtils.getShowPerAppThemeNewTag(this)) { + View tag = mBottomActionsLayout.findViewById(R.id.new_tag); + if (tag != null) { + tag.setVisibility(View.VISIBLE); + } + } + } + + public void showSaveApplyButton() { + if (mSaveApplyLayout != null && mSaveApplyLayout.getVisibility() != View.VISIBLE) { + mHandler.post(new Runnable() { + @Override + public void run() { + int navBarHeight = 0; + if (Utils.hasNavigationBar(ChooserActivity.this.getApplicationContext())) { + navBarHeight = ChooserActivity.this.getResources() + .getDimensionPixelSize(R.dimen.navigation_bar_height); + } + mSaveApplyLayout.setTranslationY(mSaveApplyLayout.getMeasuredHeight()); + mSaveApplyLayout.setVisibility(View.VISIBLE); + mSaveApplyLayout.animate() + .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION) + .setInterpolator( + new DecelerateInterpolator( + ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR)) + .translationY(-mSelector.getMeasuredHeight() + + navBarHeight); + } + }); + } + } + + public void hideSaveApplyButton() { + if (mSaveApplyLayout.getVisibility() != View.GONE) { + Animation anim = AnimationUtils.loadAnimation(this, + R.anim.component_selection_animate_out); + mSaveApplyLayout.startAnimation(anim); + anim.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + mSaveApplyLayout.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + } + } + + private void hideBottomActionsLayout() { + final ViewPropertyAnimator anim = mBottomActionsLayout.animate(); + anim.alpha(0f).setDuration(ANIMATE_SHOP_THEMES_HIDE_DURATION); + anim.setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mBottomActionsLayout.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } + + private void showBottomActionsLayout() { + mBottomActionsLayout.setVisibility(View.VISIBLE); + final ViewPropertyAnimator anim = mBottomActionsLayout.animate(); + anim.setListener(null); + anim.alpha(1f).setStartDelay(ThemeFragment.ANIMATE_DURATION) + .setDuration(ANIMATE_SHOP_THEMES_SHOW_DURATION); + } + + private void lauchGetThemes() { + String playStoreUrl = getString(R.string.play_store_url); + String wikiUrl = getString(R.string.wiki_url); + + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(playStoreUrl)); + + // Try to launch play store + if (intent.resolveActivity(getPackageManager()) != null) { + startActivity(intent); + } else { + // If no play store, try to open wiki url + intent.setData(Uri.parse(wikiUrl)); + if (intent.resolveActivity(getPackageManager()) != null) { + startActivity(intent); + } else { + Toast.makeText(this, R.string.get_more_app_not_available, + Toast.LENGTH_LONG).show(); + } + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + if (Utils.isRecentTaskHome(this)) { + mContainer.setAlpha(0f); + mBottomActionsLayout.setAlpha(0f); + mAnimateContentIn = true; + mAnimateContentInDelay = ANIMATE_CONTENT_DELAY; + } + handleIntent(intent); + } + + private void handleIntent(Intent intent) { + String action = intent.getAction(); + if ((Intent.ACTION_MAIN.equals(action) || ACTION_APPLY_THEME.equals(action)) + && intent.hasExtra(EXTRA_PKGNAME)) { + if (intent.hasExtra(EXTRA_COMPONENTS)) { + mComponentsToApply = intent.getStringArrayListExtra(EXTRA_COMPONENTS); + } else { + mComponentsToApply = null; + } + mSelectedTheme = mComponentsToApply != null ? + PreferenceUtils.getAppliedBaseTheme(this) : + getSelectedTheme(intent.getStringExtra(EXTRA_PKGNAME)); + if (mPager != null) { + startLoader(LOADER_ID_INSTALLED_THEMES); + if (mExpanded) { + int collapseDelay = ThemeFragment.ANIMATE_START_DELAY; + if (mSelector.isEnabled()) { + // onBackPressed() has all the necessary logic for collapsing the + // component selector, so we call it here. + onBackPressed(); + collapseDelay += ThemeFragment.ANIMATE_DURATION; + } + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + collapse(false); + } + }, collapseDelay); + } + } + + if (ACTION_APPLY_THEME.equals(action) && + getCallingPackage() != null && + PackageManager.PERMISSION_GRANTED == + getPackageManager() + .checkPermission(Manifest.permission.WRITE_THEMES, + getCallingPackage())) { + mThemeToApply = intent.getStringExtra(EXTRA_PKGNAME); + } + } else if (ACTION_PICK_LOCK_SCREEN_WALLPAPER.equals(action)) { + mShowLockScreenWallpaper = true; + } + } + + private String getSelectedTheme(String requestedTheme) { + String[] projection = { ThemesColumns.PRESENT_AS_THEME }; + String selection = ThemesColumns.PKG_NAME + "=?"; + String[] selectionArgs = { requestedTheme }; + + String selectedTheme = PreferenceUtils.getAppliedBaseTheme(this); + + Cursor cursor = getContentResolver().query(ThemesColumns.CONTENT_URI, + projection, selection, selectionArgs, null); + if (cursor != null) { + if (cursor.getCount() > 0 && cursor.moveToFirst()) { + if (cursor.getInt(0) == 1) { + selectedTheme = requestedTheme; + } + } + cursor.close(); + } + return selectedTheme; + } + + private void setAnimatingStateAndScheduleFinish() { + mIsAnimating = true; + mContainer.setIsAnimating(true); + mHandler.postDelayed(new Runnable() { + public void run() { + mIsAnimating = false; + mContainer.setIsAnimating(false); + if (mRestartLoaderOnCollapse) { + mRestartLoaderOnCollapse = false; + startLoader(LOADER_ID_INSTALLED_THEMES); + } + } + }, FINISH_ANIMATION_DELAY); + if (mExpanded) { + hideBottomActionsLayout(); + } else { + showBottomActionsLayout(); + } + } + + private void setCustomBackground(final ImageView iv, final boolean animate) { + final Context context = ChooserActivity.this; + iv.post(new Runnable() { + @Override + public void run() { + Bitmap tmpBmp; + try { + tmpBmp = Utils.getRegularWallpaperBitmap(context); + } catch (Throwable e) { + Log.w(TAG, "Failed to retrieve wallpaper", e); + tmpBmp = null; + } + // Show the grid background if no wallpaper is set. + // Note: no wallpaper is actually a 1x1 pixel black bitmap + if (tmpBmp == null || tmpBmp.getWidth() <= 1 || tmpBmp.getHeight() <= 1) { + iv.setImageResource(R.drawable.bg_grid); + // We need to change the ScaleType to FIT_XY otherwise the background is cut + // off a bit at the bottom. + iv.setScaleType(ImageView.ScaleType.FIT_XY); + return; + } + + // Since we are applying a blur, we can afford to scale the bitmap down and use a + // smaller blur radius. + Bitmap inBmp = Bitmap.createScaledBitmap(tmpBmp, tmpBmp.getWidth() / 4, + tmpBmp.getHeight() / 4, false); + Bitmap outBmp = Bitmap.createBitmap(inBmp.getWidth(), inBmp.getHeight(), + Bitmap.Config.ARGB_8888); + + // Blur the original bitmap + RenderScript rs = RenderScript.create(context); + ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); + Allocation tmpIn = Allocation.createFromBitmap(rs, inBmp); + Allocation tmpOut = Allocation.createFromBitmap(rs, outBmp); + theIntrinsic.setRadius(5.0f); + theIntrinsic.setInput(tmpIn); + theIntrinsic.forEach(tmpOut); + tmpOut.copyTo(outBmp); + + // Create a bitmap drawable and use a color matrix to de-saturate the image + BitmapDrawable[] layers = new BitmapDrawable[2]; + layers[0] = new BitmapDrawable(getResources(), tmpBmp); + layers[1] = new BitmapDrawable(getResources(), outBmp); + ColorMatrix cm = new ColorMatrix(); + cm.setSaturation(0); + Paint p = layers[0].getPaint(); + p.setColorFilter(new ColorMatrixColorFilter(cm)); + p = layers[1].getPaint(); + p.setColorFilter(new ColorMatrixColorFilter(cm)); + TransitionDrawable d = new TransitionDrawable(layers); + + // All done + iv.setScaleType(ImageView.ScaleType.CENTER_CROP); + if (!animate) { + iv.setImageDrawable(layers[1]); + } else { + iv.setImageDrawable(d); + } + } + }); + } + + /** + * Disable the ViewPager while a theme change is occuring + */ + public void themeChangeStart() { + lockPager(); + mThemeChanging = true; + ThemeFragment f = getCurrentFragment(); + if (f != null) { + mAppliedBaseTheme = f.getThemePackageName(); + PreferenceUtils.setAppliedBaseTheme(this, mAppliedBaseTheme); + } + } + + /** + * Re-enable the ViewPager and update the "My theme" fragment if available + */ + public void themeChangeEnd(boolean isSuccess) { + mThemeChanging = false; + ThemeFragment f = getCurrentFragment(); + if (f != null) { + // We currently need to recreate the adapter in order to load + // the changes otherwise the adapter returns the original fragments + // TODO: We'll need a better way to handle this to provide a good UX + if (!(f instanceof MyThemeFragment)) { + mAdapter = new ThemesAdapter(); + mPager.setAdapter(mAdapter); + } + if (!isSuccess) { + mAppliedBaseTheme = null; + } + startLoader(LOADER_ID_APPLIED); + } + unlockPager(); + } + + public void lockPager() { + mPager.setScrollingEnabled(false); + } + + public void unlockPager() { + mPager.setScrollingEnabled(true); + } + + public ComponentSelector getComponentSelector() { + return mSelector; + } + + public void showComponentSelector(String component, View v) { + showComponentSelector(component, null, DEFAULT_COMPONENT_ID, v); + } + + public void showComponentSelector(String component, String selectedPkgName, + long selectedCmpntId, View v) { + if (component != null) { + final Resources res = getResources(); + int itemsPerPage = res.getInteger(R.integer.default_items_per_page); + int height = res.getDimensionPixelSize(R.dimen.component_selection_cell_height); + if (MODIFIES_BOOT_ANIM.equals(component)) { + itemsPerPage = res.getInteger(R.integer.bootani_items_per_page); + height = res.getDimensionPixelSize( + R.dimen.component_selection_cell_height_boot_anim); + } else if (MODIFIES_ALARMS.equals(component) || + MODIFIES_NOTIFICATIONS.equals(component) || + MODIFIES_RINGTONES.equals(component)) { + itemsPerPage = 2; + height = res.getDimensionPixelSize( + R.dimen.component_selection_cell_height_sounds); + } + if (mSaveApplyLayout.getVisibility() == View.VISIBLE) { + if (mSaveApplyLayout.getTranslationY() + height != 0) { + mSaveApplyLayout.animate() + .translationY(-height) + .setInterpolator( + new DecelerateInterpolator( + ANIMATE_SAVE_APPLY_DECELERATE_INTERPOLATOR_FACTOR)) + .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION); + } + } + mSelector.show(component, selectedPkgName, selectedCmpntId, itemsPerPage, height); + + // determine if we need to shift the cards up + int[] coordinates = new int[2]; + v.getLocationOnScreen(coordinates); + final int top = coordinates[1]; + final int bottom = coordinates[1] + v.getHeight(); + final int statusBarHeight = res.getDimensionPixelSize(R.dimen.status_bar_height); + int selectorTop = getWindowManager().getDefaultDisplay().getHeight() - height; + if (bottom > selectorTop) { + slideContentIntoView(bottom - selectorTop, height); + } else if (top < statusBarHeight) { + slideContentIntoView(top - statusBarHeight, height); + } + } + } + + public void expand() { + if (!mExpanded && !mIsAnimating) { + mExpanded = true; + mContainer.setClickable(false); + mContainer.expand(); + ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.expand(); + } + setAnimatingStateAndScheduleFinish(); + } + } + + public void collapse(final boolean applyTheme) { + mExpanded = false; + final ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.fadeOutCards(new Runnable() { + public void run() { + mContainer.collapse(); + f.collapse(applyTheme); + } + }); + } + setAnimatingStateAndScheduleFinish(); + } + + public void pickExternalWallpaper() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType(TYPE_IMAGE); + startActivityForResult(intent, REQUEST_PICK_WALLPAPER_IMAGE); + mIsPickingImage = true; + } + + public void pickExternalLockscreen() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType(TYPE_IMAGE); + startActivityForResult(intent, REQUEST_PICK_LOCKSCREEN_IMAGE); + mIsPickingImage = true; + } + + public void uninstallTheme(String pkgName) { + PackageManager pm = getPackageManager(); + pm.deletePackage(pkgName, new PackageDeleteObserver(), PackageManager.DELETE_ALL_USERS); + } + + private void slideContentIntoView(int yDelta, int selectorHeight) { + ThemeFragment f = getCurrentFragment(); + if (f != null) { + final int offset = getResources().getDimensionPixelSize(R.dimen.content_offset_padding); + if (yDelta > 0) { + yDelta += offset; + } else { + yDelta -= offset; + } + f.slideContentIntoView(yDelta, selectorHeight); + mContainerYOffset = yDelta; + } + } + + private void slideContentBack(final int yDelta) { + ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.slideContentBack(yDelta); + } + } + + @Override + protected void onResume() { + super.onResume(); + setCustomBackground(mCustomBackground, mAnimateContentIn); + // clear out any notifications that are being displayed. + NotificationHelper.cancelNotifications(this); + + ThemeManager tm = ThemeManager.getInstance(this); + mThemeChanging = tm.isThemeApplying(); + + if (!mIsPickingImage) { + startLoader(LOADER_ID_APPLIED); + } else { + mIsPickingImage = false; + } + + IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); + registerReceiver(mWallpaperChangeReceiver, filter); + } + + @Override + public void onBackPressed() { + final ThemeFragment f = getCurrentFragment(); + if (mSelector.isEnabled()) { + mSelector.hide(); + if (mContainerYOffset != 0) { + slideContentBack(-mContainerYOffset); + mContainerYOffset = 0; + } + if (f != null) f.fadeInCards(); + if (mShowLockScreenWallpaper) { + mShowLockScreenWallpaper = false; + mSelector.resetComponentType(); + } + } else if (mExpanded) { + if (mIsAnimating) { + return; + } + + if (mSaveApplyLayout.getVisibility() == View.VISIBLE) { + hideSaveApplyButton(); + if (f != null) f.clearChanges(); + } + collapse(false); + } else { + if (f != null && f.isShowingConfirmCancelOverlay()) { + f.hideConfirmCancelOverlay(); + } else if (f != null && f.isShowingCustomizeResetLayout()) { + f.hideCustomizeResetLayout(); + } else { + super.onBackPressed(); + } + } + } + + @Override + public void onPause() { + super.onPause(); + unregisterReceiver(mWallpaperChangeReceiver); + ThemeFragment f = getCurrentFragment(); + if (f != null) { + mSelectedTheme = f.getThemePackageName(); + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + @Override + protected void onStart() { + super.onStart(); + if (mTypefaceHelperCache.getTypefaceCount() <= 0) { + new TypefacePreloadTask().execute(); + } + mAnimateContentInDelay = ANIMATE_CONTENT_DELAY; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_WALLPAPER_IMAGE) { + if (data != null && data.getData() != null) { + Uri uri = data.getData(); + ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.setWallpaperImageUri(uri); + showSaveApplyButton(); + } + } + } else if (resultCode == RESULT_OK && requestCode == REQUEST_PICK_LOCKSCREEN_IMAGE) { + if (data != null && data.getData() != null) { + Uri uri = data.getData(); + ThemeFragment f = getCurrentFragment(); + if (f != null) { + f.setLockscreenImageUri(uri); + showSaveApplyButton(); + } + } + } else if (requestCode == REQUEST_SYSTEM_WINDOW_PERMISSION) { + if (Settings.canDrawOverlays(this)) { + launchAppThemer(); + } + } + } + + private void animateContentIn() { + Drawable d = mCustomBackground.getDrawable(); + if (d instanceof TransitionDrawable) { + ((TransitionDrawable) d).startTransition((int) ANIMATE_CONTENT_IN_BLUR_DURATION); + } + + if (!mShowLockScreenWallpaper) { + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f) + .setDuration(ANIMATE_CONTENT_IN_ALPHA_DURATION)) + .with(ObjectAnimator.ofFloat(mContainer, "scaleX", 2f, 1f) + .setDuration(ANIMATE_CONTENT_IN_SCALE_DURATION)) + .with(ObjectAnimator.ofFloat(mContainer, "scaleY", 2f, 1f) + .setDuration(ANIMATE_CONTENT_IN_SCALE_DURATION)); + set.setStartDelay(mAnimateContentInDelay); + set.start(); + mBottomActionsLayout.setAlpha(0f); + mBottomActionsLayout.animate().alpha(1f).setStartDelay(mAnimateContentInDelay) + .setDuration(ANIMATE_CONTENT_IN_ALPHA_DURATION); + } else { + mContainer.setAlpha(0f); + mContainer.setVisibility(View.GONE); + } + mAnimateContentIn = false; + } + + private View.OnClickListener mPagerClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + ThemeFragment f = getCurrentFragment(); + if (f != null && !mThemeChanging) { + f.performClick(mPager.isClickedOnContent()); + } + } + }; + + private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (mCustomBackground != null) setCustomBackground(mCustomBackground, false); + } + }; + + private ComponentSelector.OnOpenCloseListener mOpenCloseListener = new ComponentSelector.OnOpenCloseListener() { + @Override + public void onSelectorOpened() { + } + + @Override + public void onSelectorClosed() { + } + + @Override + public void onSelectorClosing() { + ThemeFragment f = getCurrentFragment(); + if (f != null && f.componentsChanged() + && mSaveApplyLayout.getVisibility() == View.VISIBLE) { + mSaveApplyLayout.animate() + .translationY(0) + .setInterpolator(new DecelerateInterpolator()) + .setDuration(ANIMATE_SAVE_APPLY_LAYOUT_DURATION); + } + } + }; + + private ThemeFragment getCurrentFragment() { + // instantiateItem will return the fragment if it already exists and not instantiate it, + // which should be the case for the current fragment. + ThemeFragment f; + try { + f = (mAdapter == null || mPager == null || mAdapter.getCount() <= 0) ? null : + (ThemeFragment) mAdapter.instantiateItem(mPager, mPager.getCurrentItem()); + } catch (Exception e) { + f = null; + Log.e(TAG, "Unable to get current fragment", e); + } + return f; + } + + private void populateCurrentTheme(Cursor c) { + c.moveToPosition(-1); + //Default to first wallpaper + mCurrentWallpaperCmpntId.value = DEFAULT_COMPONENT_ID; + // clear out the previous map + mCurrentTheme.clear(); + while(c.moveToNext()) { + int mixkeyIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_KEY); + int pkgIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_VALUE); + int cmpntIdIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_COMPONENT_ID); + String mixkey = c.getString(mixkeyIdx); + String component = ThemesContract.MixnMatchColumns.mixNMatchKeyToComponent(mixkey); + String pkg = c.getString(pkgIdx); + mCurrentTheme.put(component, pkg); + if (TextUtils.equals(component, ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) { + mCurrentTheme.remove(ThemesColumns.MODIFIES_LOCKSCREEN); + } + if (TextUtils.equals(component, ThemesColumns.MODIFIES_LOCKSCREEN)) { + mCurrentTheme.remove(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN); + } + if (cmpntIdIdx >= 0 && TextUtils.equals(component, ThemesColumns.MODIFIES_LAUNCHER)) { + mCurrentWallpaperCmpntId.value = c.getLong(cmpntIdIdx); + } + } + } + + private View.OnClickListener mOnShopThemesClicked = new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(); + intent.setClassName(THEME_STORE_PACKAGE, THEME_STORE_ACTIVITY); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + lauchGetThemes(); + } + } + }; + + private void startLoader(int loaderId) { + final LoaderManager manager = getSupportLoaderManager(); + final Loader loader = manager.getLoader(loaderId); + if (loader != null) { + manager.restartLoader(loaderId, null, this); + } else { + manager.initLoader(loaderId, null, this); + } + } + + @Override + public void onLoadFinished(Loader loader, Cursor data) { + if (mThemeChanging) return; + + if (mExpanded && !mActivityResuming) { + mRestartLoaderOnCollapse = true; + return; + } + + switch (loader.getId()) { + case LOADER_ID_INSTALLED_THEMES: + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + int selectedThemeIndex = -1; + if (TextUtils.isEmpty(mSelectedTheme)) mSelectedTheme = mAppliedBaseTheme; + while(data.moveToNext()) { + if (mSelectedTheme.equals(data.getString( + data.getColumnIndex(ThemesColumns.PKG_NAME)))) { + // we need to add one here since the first card is "My theme" + selectedThemeIndex = data.getPosition(); + mSelectedTheme = null; + break; + } + } + data.moveToFirst(); + mAdapter.swapCursor(data); + mAdapter.notifyDataSetChanged(); + if (selectedThemeIndex >= 0) { + mPager.setCurrentItem(selectedThemeIndex, false); + + if (mThemeToApply != null) { + ThemeFragment f = getCurrentFragment(); + f.applyThemeWhenPopulated(mThemeToApply, mComponentsToApply); + mThemeToApply = null; + } + } + if (mAnimateContentIn) animateContentIn(); + mActivityResuming = true; + break; + case LOADER_ID_APPLIED: + startLoader(LOADER_ID_INSTALLED_THEMES); + populateCurrentTheme(data); + break; + } + } + + @Override + public void onLoaderReset(Loader loader) { + switch (loader.getId()) { + case LOADER_ID_INSTALLED_THEMES: + mAdapter.swapCursor(null); + mAdapter.notifyDataSetChanged(); + break; + } + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_INSTALLED_THEMES: + mAppliedBaseTheme = PreferenceUtils.getAppliedBaseTheme(this); + break; + case LOADER_ID_APPLIED: + //TODO: Mix n match query should only be done once + break; + } + return CursorLoaderHelper.chooserActivityCursorLoader(this, id, mAppliedBaseTheme); + } + + public Map getSelectedComponentsMap() { + return getCurrentFragment().getSelectedComponentsMap(); + } + + public class ThemesAdapter extends NewFragmentStatePagerAdapter { + private ArrayList mInstalledThemes; + private String mAppliedThemeTitle; + private String mAppliedThemeAuthor; + private HashMap mRepositionedFragments; + + public ThemesAdapter() { + super(getSupportFragmentManager()); + mRepositionedFragments = new HashMap(); + } + + @Override + public Fragment getItem(int position) { + ThemeFragment f = null; + MutableLong wallpaperCmpntId; + if (mInstalledThemes != null) { + final String pkgName = mInstalledThemes.get(position); + if (pkgName.equals(mAppliedBaseTheme)) { + f = MyThemeFragment.newInstance(mAppliedBaseTheme, mAppliedThemeTitle, + mAppliedThemeAuthor, mAnimateContentIn, mShowLockScreenWallpaper); + wallpaperCmpntId = mCurrentWallpaperCmpntId; + } else { + f = ThemeFragment.newInstance(pkgName, mAnimateContentIn); + wallpaperCmpntId = new MutableLong(DEFAULT_COMPONENT_ID); + } + f.setCurrentTheme(mCurrentTheme, wallpaperCmpntId); + } + return f; + } + + @Override + public long getItemId(int position) { + if (mInstalledThemes != null) { + final String pkgName = mInstalledThemes.get(position); + return pkgName.hashCode(); + } + return 0; + } + + @Override + public int getItemPosition(Object object) { + final ThemeFragment f = (ThemeFragment) object; + final String pkgName = f != null ? f.getThemePackageName() : null; + if (pkgName != null && mRepositionedFragments.containsKey(pkgName)) { + final int position = mRepositionedFragments.get(pkgName); + mRepositionedFragments.remove(pkgName); + return position; + } + return super.getItemPosition(object); + } + + /** + * The first card should be the user's currently applied theme components so we + * will always return at least 1 or mCursor.getCount() + 1 + * @return + */ + public int getCount() { + return mInstalledThemes == null ? 0 : mInstalledThemes.size(); + } + + public void swapCursor(Cursor c) { + if (c != null && c.getCount() != 0) { + ArrayList previousOrder = mInstalledThemes == null ? null + : new ArrayList(mInstalledThemes); + mInstalledThemes = new ArrayList(c.getCount()); + mRepositionedFragments.clear(); + c.moveToPosition(-1); + while (c.moveToNext()) { + final int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + final String pkgName = c.getString(pkgIdx); + if (pkgName.equals(mAppliedBaseTheme)) { + final int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); + final int authorIdx = c.getColumnIndex(ThemesColumns.AUTHOR); + mAppliedThemeTitle = c.getString(titleIdx); + mAppliedThemeAuthor = c.getString(authorIdx); + } + mInstalledThemes.add(pkgName); + + // track any themes that may have changed position + if (previousOrder != null && previousOrder.contains(pkgName)) { + int index = previousOrder.indexOf(pkgName); + if (index != c.getPosition()) { + mRepositionedFragments.put(pkgName, c.getPosition()); + } + } else { + mRepositionedFragments.put(pkgName, c.getPosition()); + } + } + // check if any themes are no longer in the new list + if (previousOrder != null) { + for (String pkgName : previousOrder) { + if (!mInstalledThemes.contains(pkgName)) { + mRepositionedFragments.put(pkgName, POSITION_NONE); + } + } + } + } else { + mInstalledThemes = null; + } + } + + public void removeTheme(String pkgName) { + if (mInstalledThemes == null) return; + + if (mInstalledThemes.contains(pkgName)) { + final int count = mInstalledThemes.size(); + final int index = mInstalledThemes.indexOf(pkgName); + // reposition all the fragments after the one being removed + for (int i = index + 1; i < count; i++) { + mRepositionedFragments.put(mInstalledThemes.get(i), i - 1); + } + // Now remove this theme and add it to mRepositionedFragments with POSITION_NONE + mInstalledThemes.remove(pkgName); + mRepositionedFragments.put(pkgName, POSITION_NONE); + // now we can call notifyDataSetChanged() + notifyDataSetChanged(); + } + } + } + + private class TypefacePreloadTask extends AsyncTask { + + @Override + protected Object doInBackground(Object[] params) { + String[] projection = { ThemesColumns.PKG_NAME }; + String selection = ThemesColumns.MODIFIES_FONTS + "=?"; + String[] selectionArgs = { "1" }; + Cursor c = getContentResolver().query(ThemesColumns.CONTENT_URI, projection, selection, + selectionArgs, null); + if (c != null) { + while (c.moveToNext()) { + mTypefaceHelperCache.getHelperForTheme(ChooserActivity.this, c.getString(0)); + } + c.close(); + } + return null; + } + } + + /** + * Internal delete callback from the system + */ + class PackageDeleteObserver extends IPackageDeleteObserver.Stub { + public void packageDeleted(final String packageName, int returnCode) throws RemoteException { + if (returnCode == PackageManager.DELETE_SUCCEEDED) { + Log.d(TAG, "Delete succeeded"); + mHandler.post(new Runnable() { + @Override + public void run() { + mAdapter.removeTheme(packageName); + } + }); + } else { + Log.e(TAG, "Delete failed with returnCode " + returnCode); + } + } + } + + public void expandContentAndAnimateLockScreenCardIn() { + mHandler.post(new Runnable() { + @Override + public void run() { + expand(); + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator.ofFloat(mContainer, "alpha", 0f, 1f) + .setDuration(ANIMATE_CARDS_IN_DURATION)); + set.setStartDelay(mAnimateContentInDelay); + set.start(); + mContainer.setVisibility(View.VISIBLE); + getCurrentFragment().showLockScreenCard(); + } + }, ANIMATE_CARDS_IN_DURATION); + } + }); + } + + private void launchAppThemer() { + PreferenceUtils.setShowPerAppThemeNewTag(ChooserActivity.this, false); + Intent intent = new Intent(ChooserActivity.this, PerAppThemingWindow.class); + startService(intent); + finish(); + } + + private void requestSystemWindowPermission() { + Intent intent = new Intent (Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + getPackageName())); + startActivityForResult(intent, REQUEST_SYSTEM_WINDOW_PERMISSION); + } +} diff --git a/src/org/cyanogenmod/theme/chooser/ComponentCardView.java b/src/org/cyanogenmod/theme/chooser/ComponentCardView.java new file mode 100644 index 0000000..54d97c9 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/ComponentCardView.java @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.IntEvaluator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.TransitionDrawable; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewOverlay; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class ComponentCardView extends LinearLayout { + public static final int CARD_FADE_DURATION = 300; + + private static final float SEMI_OPAQUE_ALPHA = 0.4f; + + protected TextView mLabel; + protected View mEmptyView; + protected View mContentView; + + // Expanded Padding + int mExpandPadLeft; + int mExpandPadTop; + int mExpandPadRight; + int mExpandPadBottom; + + // The background drawable is returning an alpha of 0 regardless of what we set it to + // so this will help us keep track of what the drawable's alpha is at. + private int mBackgroundAlpha = 255; + + public ComponentCardView(Context context) { + this(context, null); + } + + public ComponentCardView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ComponentCardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setEnabled(false); + } + + @Override + protected void onFinishInflate() { + mLabel = (TextView) findViewById(R.id.label); + + Resources r = getContext().getResources(); + mExpandPadLeft = + (int) r.getDimension(R.dimen.card_padding_left_right) + getPaddingLeft(); + mExpandPadTop = + (int) r.getDimension(R.dimen.card_padding_top) + getPaddingTop(); + mExpandPadRight = + (int) r.getDimension(R.dimen.card_padding_left_right) + getPaddingRight(); + mExpandPadBottom = + (int) r.getDimension(R.dimen.card_padding_bottom) + getPaddingBottom(); + mEmptyView = findViewById(R.id.empty); + mContentView = findViewById(R.id.content); + } + + public void expand(boolean showLabel) { + setEnabled(true); + TransitionDrawable bg = null; + if (getBackground() instanceof TransitionDrawable) { + bg = (TransitionDrawable) getBackground(); + } + if (bg != null) { + Rect paddingRect = new Rect(); + bg.getPadding(paddingRect); + } + + setPadding(mExpandPadLeft, 0, mExpandPadRight, mExpandPadBottom); + + if (mLabel != null) { + mLabel.setAlpha(showLabel ? 1f : 0f); + mLabel.setVisibility(View.VISIBLE); + } + } + + public void animateExpand() { + if (getBackground() instanceof TransitionDrawable) { + TransitionDrawable background = (TransitionDrawable) getBackground(); + if (mLabel != null) { + mLabel.setVisibility(View.VISIBLE); + mLabel.setAlpha(0f); + mLabel.animate().alpha(1f).setDuration(CARD_FADE_DURATION).start(); + } + background.startTransition(CARD_FADE_DURATION); + } + } + + public void collapse() { + setEnabled(false); + if (mLabel != null) { + mLabel.setVisibility(View.GONE); + } + setPadding(0, 0, 0, 0); + } + + public void animateFadeOut() { + if (mLabel != null) { + mLabel.animate().alpha(0f).setDuration(CARD_FADE_DURATION); + } + + if (getBackground() instanceof TransitionDrawable) { + TransitionDrawable background = (TransitionDrawable) getBackground(); + background.reverseTransition(CARD_FADE_DURATION); + } + } + + /** + * Animates the card background and the title to 20% opacity. + */ + public void animateCardFadeOut() { + this.animate().alpha(SEMI_OPAQUE_ALPHA).setDuration(CARD_FADE_DURATION); + } + + /** + * Animates the card background and the title back to full opacity. + */ + public void animateCardFadeIn() { + this.animate().alpha(1f).setDuration(CARD_FADE_DURATION); + } + + /** + * Animates a change in the content of the card + * @param v View in card to animate + * @param overlay Drawable to animate as a ViewOverlay + * @param duration Duration of animation + */ + public void animateContentChange(View v, final Drawable overlay, long duration) { + final ViewOverlay viewOverlay = this.getOverlay(); + viewOverlay.add(overlay); + final int x = getRelativeLeft(v); + final int y = getRelativeTop(v); + final int width = v.getWidth(); + final int height = v.getHeight(); + overlay.setBounds(x, y, x + v.getWidth(), y + v.getHeight()); + + final ValueAnimator overlayAnimator = ValueAnimator.ofFloat(1f, 0f); + overlayAnimator.setDuration(duration); + overlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (Float) animation.getAnimatedValue(); + overlay.setAlpha((int) (255 * value)); + int newWidth = (int) (value * width); + int newHeight = (int) (value * height); + int dw = (width - newWidth) / 2; + int dh = (height - newHeight) / 2; + overlay.setBounds(x + dw, y + dh, x + dw + newWidth, y + dh + newHeight); + } + }); + overlayAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + // Clear out the ViewOverlay now that we are done animating + viewOverlay.clear(); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }); + + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator.ofFloat(v, "alpha", 0f, 1f)) + .with(ObjectAnimator.ofFloat(v, "scaleX", 0f, 1f)) + .with(ObjectAnimator.ofFloat(v, "scaleY", 0f, 1f)); + set.setDuration(duration); + + set.start(); + overlayAnimator.start(); + } + + public void setEmptyViewEnabled(boolean enabled) { + if (mEmptyView != null) { + mEmptyView.setVisibility(enabled ? View.VISIBLE : View.GONE); + } + if (mContentView != null) { + mContentView.setVisibility(enabled ? View.INVISIBLE : View.VISIBLE); + } + } + + public boolean isShowingEmptyView() { + return mEmptyView != null && mEmptyView.getVisibility() == View.VISIBLE; + } + + private int getRelativeTop(View v) { + if (v.getParent() == this) { + return v.getTop(); + } else { + return v.getTop() + ((View) v.getParent()).getTop(); + } + } + + private int getRelativeLeft(View v) { + if (v.getParent() == this) { + return v.getLeft(); + } else { + return v.getLeft() + ((View) v.getParent()).getLeft(); + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/ComponentSelector.java b/src/org/cyanogenmod/theme/chooser/ComponentSelector.java new file mode 100644 index 0000000..ad0b68c --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/ComponentSelector.java @@ -0,0 +1,934 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.ContentObserver; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.Animation.AnimationListener; +import android.view.animation.AnimationUtils; +import android.widget.ComponentSelectorLinearLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import org.cyanogenmod.theme.util.AudioUtils; +import org.cyanogenmod.theme.util.CursorLoaderHelper; +import org.cyanogenmod.theme.util.ThemedTypefaceHelper; +import org.cyanogenmod.theme.util.TypefaceHelperCache; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.providers.ThemesContract; +import cyanogenmod.providers.ThemesContract.PreviewColumns; +import cyanogenmod.providers.ThemesContract.ThemesColumns; + +import java.util.Map; + +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; + +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_STATUS_BAR; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_FONT; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ICONS; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_WALLPAPER; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_NAVIGATION_BAR; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_LOCKSCREEN; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_STYLE; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_BOOT_ANIMATION; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_RINGTONE; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_NOTIFICATION; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ALARM; + +public class ComponentSelector extends LinearLayout + implements LoaderManager.LoaderCallbacks { + private static final String TAG = ComponentSelector.class.getSimpleName(); + + public static final boolean DEBUG_SELECTOR = false; + + public static final String EXTERNAL_WALLPAPER = "external"; + + public static final String MOD_LOCK = "mod_lock"; + + private static final int EXTRA_WALLPAPER_COMPONENTS = 2; + + private static final int EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS = 3; + + protected static final long DEFAULT_COMPONENT_ID = 0; + + private Context mContext; + private LayoutInflater mInflater; + private ComponentSelectorLinearLayout mContent; + private LinearLayout.LayoutParams mItemParams; + private LinearLayout.LayoutParams mSoundItemParams = + new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1.0f); + + private String mComponentType; + private int mBatteryStyle; + private int mItemsPerPage; + private String mAppliedComponentPkgName; + private String mSelectedComponentPkgName; + private long mSelectedComponentId; + + // animations for bringing selector in and out of view + private Animation mAnimateIn; + private Animation mAnimateOut; + + private OnItemClickedListener mListener; + + private OnOpenCloseListener mOpenCloseListener; + + private MediaPlayer mMediaPlayer; + private ImageView mCurrentPlayPause; + + private TypefaceHelperCache mTypefaceCache; + + private ThemesObserver mThemesObserver; + + public static final String IS_LIVE_LOCK_SCREEN_VIEW = "is_live_lock_screen_view"; + private View mPrevLockScreenView; + + public ComponentSelector(Context context, AttributeSet attrs) { + super(context, attrs); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ComponentSelector); + mItemsPerPage = a.getInt(R.styleable.ComponentSelector_itemsPerPage, + context.getResources().getInteger(R.integer.default_items_per_page)); + a.recycle(); + + mContext = context; + mInflater = LayoutInflater.from(context); + // TODO: Load from settings once available + mBatteryStyle = 0; /*Settings.System.getInt(context.getContentResolver(), + Settings.System.STATUS_BAR_BATTERY_STYLE, 0);*/ + + mAnimateIn = AnimationUtils.loadAnimation(mContext, + R.anim.component_selection_animate_in); + mAnimateOut = AnimationUtils.loadAnimation(mContext, + R.anim.component_selection_animate_out); + mAnimateIn.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + if (mOpenCloseListener != null) mOpenCloseListener.onSelectorOpened(); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + mAnimateOut.setAnimationListener(new AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + if (mOpenCloseListener != null) mOpenCloseListener.onSelectorClosing(); + } + + @Override + public void onAnimationEnd(Animation animation) { + setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + mMediaPlayer = new MediaPlayer(); + mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + if (mCurrentPlayPause != null) { + mCurrentPlayPause.setImageResource(R.drawable.media_sound_selector_preview); + mCurrentPlayPause = null; + } + } + }); + mTypefaceCache = TypefaceHelperCache.getInstance(); + mThemesObserver = new ThemesObserver(); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mContent = (ComponentSelectorLinearLayout) findViewById(R.id.content); + setEnabled(false); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mThemesObserver.register(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mThemesObserver.unregister(); + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + // Window visibility transitions as follows + // VISIBLE -> INVISIBLE -> GONE + // GONE -> INVISIBLE -> VISIBLE + if (visibility == View.GONE) { + mThemesObserver.unregister(); + } else if (visibility == View.VISIBLE) { + mThemesObserver.register(); + } + } + + public void resetComponentType() { + mComponentType = null; + } + + public void setComponentType(String component) { + setComponentType(component, null, DEFAULT_COMPONENT_ID); + } + + public void setComponentType(String component, String selectedPkgName) { + setComponentType(component, null, DEFAULT_COMPONENT_ID); + } + + public void setComponentType(String component, String selectedPkgName, + long selectedComponentId) { + mSelectedComponentPkgName = selectedPkgName; + mSelectedComponentId = selectedComponentId; + if (mComponentType == null || !mComponentType.equals(component)) { + mContent.removeAllViews(); + mComponentType = component; + ((FragmentActivity) mContext).getSupportLoaderManager().restartLoader( + getLoaderIdFromComponent(component), null, this); + } else if (mComponentType != null) { + int count = mContent.getChildCount(); + final Resources res = getResources(); + boolean highlightTitle; + Map selectedComponents = null; + if (mComponentType.equals(MODIFIES_LOCKSCREEN)) { + selectedComponents = ((ChooserActivity)mContext) + .getSelectedComponentsMap(); + } + for (int i = 0; i < count; i++) { + final View child = mContent.getChildAt(i); + final TextView tv = (TextView) child.findViewById(R.id.title); + // The child itself may have the tag, or in the case of sounds its the grandchild, + // either way the parent of the textview will have the pkgName in its tag + final View viewWithTag = (View) tv.getParent(); + final String pkgName = (String) viewWithTag.getTag(R.id.tag_key_package_name); + Long cmpntId = (Long) viewWithTag.getTag(R.id.tag_key_component_id); + if (cmpntId == null) { + cmpntId = DEFAULT_COMPONENT_ID; + } + Boolean isLLS = (Boolean) viewWithTag.getTag(R.id.tag_key_live_lock_screen); + highlightTitle = false; + if (selectedComponents != null && isLLS != null) { + if ((TextUtils.equals(selectedComponents.get(MODIFIES_LOCKSCREEN), pkgName) + && !isLLS) || (TextUtils.equals(selectedComponents.get( + MODIFIES_LIVE_LOCK_SCREEN), pkgName) && isLLS)) { + highlightTitle = true; + } + } else if (pkgName.equals(selectedPkgName) && cmpntId == selectedComponentId) { + highlightTitle = true; + } + if (highlightTitle) { + tv.setTextColor(res.getColor(R.color.component_selection_current_text_color)); + } else { + tv.setTextColor(res.getColor(android.R.color.white)); + } + } + } + } + + public String getComponentType() { + return mComponentType; + } + + public void setNumItemsPerPage(int itemsPerPage) { + if (mItemsPerPage != itemsPerPage) { + mItemsPerPage = itemsPerPage; + } + } + + public void setHeight(int height) { + ViewGroup.LayoutParams params = mContent.getLayoutParams(); + if (params.height != height) { + params.height = height; + mContent.setLayoutParams(params); + requestLayout(); + } + } + + public void show(String componentType, int itemsPerPage, int height) { + show(componentType, null, itemsPerPage, height); + } + + public void show(String componentType, String selectedPkgName, int itemsPerPage, int height) { + show(componentType, selectedPkgName, DEFAULT_COMPONENT_ID, itemsPerPage, height); + } + + public void show(String componentType, String selectedPkgName, long selectedComponentId, + int itemsPerPage, int height) { + setNumItemsPerPage(itemsPerPage); + setHeight(height); + setComponentType(componentType, selectedPkgName, selectedComponentId); + show(); + } + + public void show() { + if (getVisibility() == View.GONE) { + setEnabled(true); + setVisibility(View.VISIBLE); + startAnimation(mAnimateIn); + } + } + + public void hide() { + if (getVisibility() == View.VISIBLE && isEnabled()) { + setEnabled(false); + startAnimation(mAnimateOut); + } + if (mMediaPlayer != null && mMediaPlayer.isPlaying()) mMediaPlayer.stop(); + } + + private int getLoaderIdFromComponent(String component) { + if (MODIFIES_STATUS_BAR.equals(component)) { + return LOADER_ID_STATUS_BAR; + } + if (MODIFIES_NAVIGATION_BAR.equals(component)) { + return LOADER_ID_NAVIGATION_BAR; + } + if (MODIFIES_FONTS.equals(component)) { + return LOADER_ID_FONT; + } + if (MODIFIES_ICONS.equals(component)) { + return LOADER_ID_ICONS; + } + if (MODIFIES_OVERLAYS.equals(component)) { + return LOADER_ID_STYLE; + } + if (MODIFIES_LAUNCHER.equals(component)) { + return LOADER_ID_WALLPAPER; + } + if (MODIFIES_BOOT_ANIM.equals(component)) { + return LOADER_ID_BOOT_ANIMATION; + } + if (MODIFIES_RINGTONES.equals(component)) { + return LOADER_ID_RINGTONE; + } + if (MODIFIES_NOTIFICATIONS.equals(component)) { + return LOADER_ID_NOTIFICATION; + } + if (MODIFIES_ALARMS.equals(component)) { + return LOADER_ID_ALARM; + } + if (MODIFIES_LOCKSCREEN.equals(component)) { + return LOADER_ID_LOCKSCREEN; + } + return -1; + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return CursorLoaderHelper.componentSelectorCursorLoader(mContext, id); + } + + @Override + public void onLoadFinished(Loader loader, final Cursor data) { + int currentLoaderId = loader.getId(); + int count = data.getCount(); + int screenWidth = mContext.getResources().getDisplayMetrics().widthPixels; + final Resources res = getResources(); + int dividerPadding = res.getDimensionPixelSize(R.dimen.component_divider_padding_top); + int dividerHeight = res.getDimensionPixelSize(R.dimen.component_divider_height); + MatrixCursor lockScreenMatrixCursor = null; + + switch (currentLoaderId) { + case LOADER_ID_ALARM: + case LOADER_ID_NOTIFICATION: + case LOADER_ID_RINGTONE: + mItemParams = new LayoutParams(screenWidth, + ViewGroup.LayoutParams.MATCH_PARENT); + // Sounds are a special case where there are two items laid out + // vertically. This layout is added as a single item so we need to + // adjust the count by dividing by the number of items per page and + // rounding up so we include all items. + count = (int) Math.ceil((double)count / mItemsPerPage); + mContent.setShowDividers(LinearLayout.SHOW_DIVIDER_NONE); + break; + case LOADER_ID_BOOT_ANIMATION: + dividerPadding = res.getDimensionPixelSize( + R.dimen.component_divider_padding_top_bootani); + dividerHeight = res.getDimensionPixelSize(R.dimen.component_divider_height_bootani); + // fall through to default + default: + mItemParams = new LayoutParams(screenWidth / mItemsPerPage, + ViewGroup.LayoutParams.MATCH_PARENT); + if (currentLoaderId == LOADER_ID_WALLPAPER || + currentLoaderId == LOADER_ID_LOCKSCREEN) { + count += EXTRA_WALLPAPER_COMPONENTS; + } + + if (currentLoaderId == LOADER_ID_LOCKSCREEN) { + lockScreenMatrixCursor = splitLockScreenCursor(data); + if (lockScreenMatrixCursor != null) { + count = lockScreenMatrixCursor.getCount() + EXTRA_WALLPAPER_COMPONENTS; + } + } + + mContent.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); + break; + } + + mContent.setDividerPadding(dividerPadding); + mContent.setDividerHeight(dividerHeight); + + new LoadItemsTask().execute((lockScreenMatrixCursor != null) + ? lockScreenMatrixCursor : data, count); + } + + /* Some themes might contain a lock wallpaper AND a live lock screen. + * ThemesProvider will return one single row containing both thumbnail paths + * (ThemesProvider groups by theme_id) so we need to create a cursor on the + * fly to split that row into 2 to properly generate a view for each thumbnail + */ + private MatrixCursor splitLockScreenCursor(Cursor data) { + int lockWallPaperThumbnailIndx, llsThumbnailIndx, pkgIndx; + String lockWallPaperThumbnail, liveLockScreenThumbnail, pkgName; + MatrixCursor lockScreenMatrixCursor; + int needToSplitRowAt = -1; + + lockWallPaperThumbnailIndx = data.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL); + llsThumbnailIndx = data.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL); + pkgIndx = data.getColumnIndex(ThemesColumns.PKG_NAME); + + if (lockWallPaperThumbnailIndx < 0 || llsThumbnailIndx < 0 || pkgIndx < 0) { + //An invalid cursor was provided, we can't continue processing + Log.e(TAG, "Failed to process cursor due to missing columns"); + return null; + } + + //Let's find out if we really need to allocate a MatrixCursor. + //If we find at least one row with valid data in lock_wallpaper_thumbnail AND + //live_lock_screen_thumbnail it means we do. + data.moveToPosition(-1); + while (data.moveToNext()) { + lockWallPaperThumbnail = data.getString(lockWallPaperThumbnailIndx); + liveLockScreenThumbnail = data.getString(llsThumbnailIndx); + if (!TextUtils.isEmpty(lockWallPaperThumbnail) + && !TextUtils.isEmpty(liveLockScreenThumbnail)) { + needToSplitRowAt = data.getPosition(); + break; + } + } + + if (needToSplitRowAt == -1) return null; + + lockScreenMatrixCursor = new MatrixCursor(data.getColumnNames()); + //Clone all the *regular* rows up to needToSplitRowAt + for (int indx = 0; indx < needToSplitRowAt; indx++) { + data.moveToPosition(indx); + lockScreenMatrixCursor.addRow(CursorLoaderHelper.getRowFromCursor(data)); + } + if (needToSplitRowAt == 0) { + data.moveToPosition(-1); + } + while (data.moveToNext()) { + lockWallPaperThumbnail = data.getString(lockWallPaperThumbnailIndx); + liveLockScreenThumbnail = data.getString(llsThumbnailIndx); + if (!TextUtils.isEmpty(lockWallPaperThumbnail) + && !TextUtils.isEmpty(liveLockScreenThumbnail)) { + pkgName = data.getString(pkgIndx); + + MatrixCursor.RowBuilder lockWallpaperRow = lockScreenMatrixCursor.newRow(); + MatrixCursor.RowBuilder liveLockScreenRow = lockScreenMatrixCursor.newRow(); + + for (String col : data.getColumnNames()) { + if (TextUtils.equals(col, PreviewColumns.LOCK_WALLPAPER_THUMBNAIL)) { + lockWallpaperRow.add(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, + lockWallPaperThumbnail); + liveLockScreenRow.add(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, null); + } else if (TextUtils.equals(col, PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL)) { + lockWallpaperRow.add(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, null); + liveLockScreenRow.add(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + liveLockScreenThumbnail); + } else if (TextUtils.equals(col, MODIFIES_LIVE_LOCK_SCREEN)) { + lockWallpaperRow.add(MODIFIES_LIVE_LOCK_SCREEN, 0); + liveLockScreenRow.add(MODIFIES_LIVE_LOCK_SCREEN, 1); + } else { + int colIndx = data.getColumnIndex(col); + lockWallpaperRow.add(col, CursorLoaderHelper.getFieldValueFromRow(data, + colIndx)); + liveLockScreenRow.add(col, CursorLoaderHelper.getFieldValueFromRow(data, + colIndx)); + } + } + } else { + //This is a regular row, so just clone it + lockScreenMatrixCursor.addRow(CursorLoaderHelper.getRowFromCursor(data)); + } + } + return lockScreenMatrixCursor; + } + + @Override + public void onLoaderReset(Loader loader) { + } + + public void setOnItemClickedListener(OnItemClickedListener listener) { + mListener = listener; + } + + public void setOnOpenCloseListener(OnOpenCloseListener listener) { + mOpenCloseListener = listener; + } + + private View newView(Cursor cursor, int position, ViewGroup container) { + if (MODIFIES_STATUS_BAR.equals(mComponentType)) { + return newStatusBarView(cursor, container, position); + } + if (MODIFIES_NAVIGATION_BAR.equals(mComponentType)) { + return newNavBarView(cursor, container, position); + } + if (MODIFIES_FONTS.equals(mComponentType)) { + return newFontView(cursor, container, position); + } + if (MODIFIES_ICONS.equals(mComponentType)) { + return newIconView(cursor, container, position); + } + if (MODIFIES_OVERLAYS.equals(mComponentType)) { + return newStyleView(cursor, container, position); + } + if (MODIFIES_LAUNCHER.equals(mComponentType)) { + return newWallpapersView(cursor, container, position, + cursor.getColumnIndex(PreviewColumns.WALLPAPER_THUMBNAIL), false, + EXTRA_WALLPAPER_COMPONENTS); + } + if (MODIFIES_BOOT_ANIM.equals(mComponentType)) { + return newBootanimationView(cursor, container, position); + } + if (MODIFIES_RINGTONES.equals(mComponentType) || + MODIFIES_NOTIFICATIONS.equals(mComponentType) || + MODIFIES_ALARMS.equals(mComponentType)) { + return newSoundView(cursor, container, position, mComponentType); + } + if (MODIFIES_LOCKSCREEN.equals(mComponentType)) { + boolean isLiveLockScreen = false; + if (position >= EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS) { + cursor.moveToPosition(position - EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS); + int liveLockIndex = cursor.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN); + isLiveLockScreen = liveLockIndex >= 0 && + cursor.getInt(liveLockIndex) == 1; + } + int index = isLiveLockScreen + ? cursor.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL) + : cursor.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL); + return newWallpapersView(cursor, container, position, index, isLiveLockScreen, + EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS); + } + return null; + } + + private View newStatusBarView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.status_bar_component_selection_item, + parent, false); + int wifiIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_ICON); + int signalIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_SIGNAL_ICON); + int bluetoothIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BLUETOOTH_ICON); + int batteryIndex = cursor.getColumnIndex(Utils.getBatteryIndex(mBatteryStyle)); + int backgroundIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.slot1)).setImageBitmap( + Utils.loadBitmapBlob(cursor, wifiIndex)); + ((ImageView) v.findViewById(R.id.slot2)).setImageBitmap( + Utils.loadBitmapBlob(cursor, signalIndex)); + ((ImageView) v.findViewById(R.id.slot3)).setImageBitmap( + Utils.loadBitmapBlob(cursor, bluetoothIndex)); + ((ImageView) v.findViewById(R.id.slot4)).setImageBitmap( + Utils.loadBitmapBlob(cursor, batteryIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.findViewById(R.id.container).setBackground( + new BitmapDrawable(Utils.loadBitmapBlob(cursor, backgroundIndex))); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newNavBarView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.navigation_bar_component_selection_item, parent, + false); + int backIndex = cursor.getColumnIndex(PreviewColumns.NAVBAR_BACK_BUTTON); + int backgroundIndex = cursor.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.back)).setImageBitmap( + Utils.loadBitmapBlob(cursor, backIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.findViewById(R.id.container).setBackground( + new BitmapDrawable(Utils.loadBitmapBlob(cursor, backgroundIndex))); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newFontView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.font_component_selection_item, parent, false); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + TextView preview = (TextView) v.findViewById(R.id.text_preview); + String pkgName = cursor.getString(pkgNameIndex); + + ThemedTypefaceHelper helper = mTypefaceCache.getHelperForTheme(mContext, pkgName); + Typeface typefaceNormal = helper.getTypeface(Typeface.NORMAL); + preview.setTypeface(typefaceNormal); + + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newIconView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.icon_component_selection_item, parent, + false); + int iconIndex = cursor.getColumnIndex(PreviewColumns.ICON_PREVIEW_1); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.icon)).setImageBitmap( + Utils.loadBitmapBlob(cursor, iconIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newStyleView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.icon_component_selection_item, parent, + false); + int styleIndex = cursor.getColumnIndex(PreviewColumns.STYLE_THUMBNAIL); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.icon)).setImageBitmap( + Utils.loadBitmapBlob(cursor, styleIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newWallpapersView(Cursor cursor, ViewGroup parent, int position, + int wallpaperIndex, boolean isLiveLockScreen, int numExtraComponents) { + View v = mInflater.inflate(R.layout.wallpaper_component_selection_item, parent, + false); + ImageView iv = (ImageView) v.findViewById(R.id.icon); + if (position == 0) { + iv.setImageResource(R.drawable.img_wallpaper_none); + v.setTag(R.id.tag_key_package_name, ""); + ((TextView) v.findViewById(R.id.title)).setText(R.string.wallpaper_none_title); + } else if (position == 1) { + iv.setImageResource(R.drawable.img_wallpaper_external); + v.setTag(R.id.tag_key_package_name, EXTERNAL_WALLPAPER); + ((TextView) v.findViewById(R.id.title)) + .setText(R.string.wallpaper_external_title); + } else if (numExtraComponents == EXTRA_LOCK_SCREEN_WALLPAPER_COMPONENTS && position == 2) { + // TODO: update drawable once the asset is provided by design + iv.setImageResource(android.R.drawable.ic_lock_lock); + v.setTag(R.id.tag_key_package_name, MOD_LOCK); + ((TextView) v.findViewById(R.id.title)) + .setText(R.string.mod_lock_title); + } else { + cursor.moveToPosition(position - numExtraComponents); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + int cmpntIdIndex = cursor.getColumnIndex(PreviewColumns.COMPONENT_ID); + long cmpntId = (cmpntIdIndex >= 0) ? + cursor.getLong(cmpntIdIndex) : DEFAULT_COMPONENT_ID; + iv.setImageBitmap( + Utils.loadBitmapBlob(cursor, wallpaperIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setTag(R.id.tag_key_component_id, cmpntId); + v.setTag(R.id.tag_key_live_lock_screen, isLiveLockScreen); + v.findViewById(R.id.live_lock_screen_badge) + .setVisibility(isLiveLockScreen ? View.VISIBLE : View.GONE); + } + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newBootanimationView(Cursor cursor, ViewGroup parent, int position) { + cursor.moveToPosition(position); + View v = mInflater.inflate(R.layout.bootani_component_selection_item, parent, + false); + int wallpaperIndex = cursor.getColumnIndex(PreviewColumns.BOOTANIMATION_THUMBNAIL); + int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + ((ImageView) v.findViewById(R.id.preview)).setImageBitmap( + Utils.loadBitmapBlob(cursor, wallpaperIndex)); + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + return v; + } + + private View newSoundView(Cursor cursor, ViewGroup parent, int position, + final String component) { + LinearLayout container = (LinearLayout) mInflater.inflate( + R.layout.component_selection_sounds_pager_item, parent, false); + container.setWeightSum(mItemsPerPage); + for (int i = 0; i < mItemsPerPage; i++) { + int index = position * mItemsPerPage + i; + if (cursor.getCount() <= index) continue; + cursor.moveToPosition(index); + View v = mInflater.inflate(R.layout.sound_component_selection_item, parent, + false); + final int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + + setTitle(((TextView) v.findViewById(R.id.title)), cursor); + v.setTag(R.id.tag_key_package_name, cursor.getString(pkgNameIndex)); + v.setOnClickListener(mItemClickListener); + container.addView(v, mSoundItemParams); + final View playButton = v.findViewById(R.id.play_button); + playButton.setTag(cursor.getString(pkgNameIndex)); + playButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + int type; + String pkgName = (String) v.getTag(); + if (component.equals(MODIFIES_RINGTONES)) { + type = RingtoneManager.TYPE_RINGTONE; + } else if (component.equals(MODIFIES_NOTIFICATIONS)) { + type = RingtoneManager.TYPE_NOTIFICATION; + } else { + type = RingtoneManager.TYPE_ALARM; + } + boolean shouldStop = playButton == mCurrentPlayPause; + try { + if (mMediaPlayer != null && mMediaPlayer.isPlaying()) { + mMediaPlayer.stop(); + if (mCurrentPlayPause != null) { + mCurrentPlayPause.setImageResource( + R.drawable.media_sound_selector_preview); + } + mCurrentPlayPause = null; + } + if (mCurrentPlayPause != playButton && !shouldStop) { + AudioUtils.loadThemeAudible(mContext, type, pkgName, + mMediaPlayer); + mMediaPlayer.start(); + mCurrentPlayPause = (ImageView) playButton; + mCurrentPlayPause.setImageResource( + R.drawable.media_sound_selector_stop); + } + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to play preview sound", e); + } + } + }); + } + + return container; + } + + private class LoadItemsTask extends AsyncTask { + + @Override + protected Void doInBackground(Object... params) { + Cursor c = (Cursor) params[0]; + int count = (Integer) params[1]; + for (int i = 0; i < count && !isCancelled(); i++) { + final View v = newView(c, i, mContent); + mContent.post(new Runnable() { + @Override + public void run() { + mContent.addView(v, mItemParams); + } + }); + } + + if (c instanceof MatrixCursor) { + c.close(); + } + + // destroy the loader now that we are done with it + ComponentSelector.this.post(new Runnable() { + @Override + public void run() { + ((FragmentActivity)mContext).getSupportLoaderManager().destroyLoader( + getLoaderIdFromComponent(mComponentType)); + } + }); + return null; + } + } + + private void setTitle(TextView titleView, Cursor cursor) { + String pkgName = cursor.getString(cursor.getColumnIndex(ThemesColumns.PKG_NAME)); + int cmpntIdIndex = cursor.getColumnIndex(PreviewColumns.COMPONENT_ID); + long cmpntId = DEFAULT_COMPONENT_ID; + if (cmpntIdIndex >= 0) { + cmpntId = cursor.getLong(cmpntIdIndex); + } + if (Utils.getDefaultThemePackageName(mContext).equals(pkgName)) { + titleView.setText(mContext.getString(R.string.default_tag_text)); + titleView.setTypeface(titleView.getTypeface(), Typeface.BOLD); + } else { + titleView.setText(cursor.getString(cursor.getColumnIndex(ThemesColumns.TITLE))); + } + boolean highlightTitle = false; + if (mComponentType.equals(MODIFIES_LOCKSCREEN)) { + Map selectedComponents = ((ChooserActivity)mContext) + .getSelectedComponentsMap(); + int isLLS = cursor.getInt(cursor.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN)); + if ((TextUtils.equals(selectedComponents.get(MODIFIES_LOCKSCREEN), pkgName) + && isLLS == 0) || (TextUtils.equals( + selectedComponents.get(MODIFIES_LIVE_LOCK_SCREEN), pkgName) && isLLS == 1)) { + highlightTitle = true; + } + } else if (pkgName.equals(mSelectedComponentPkgName) && cmpntId == mSelectedComponentId) { + highlightTitle = true; + } + if (highlightTitle) { + titleView.setTextColor(getResources().getColor( + R.color.component_selection_current_text_color)); + } + } + + private OnClickListener mItemClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + long cmpntId = DEFAULT_COMPONENT_ID; + String pkgName = (String) v.getTag(R.id.tag_key_package_name); + Long cmpntIdTag = (Long) v.getTag(R.id.tag_key_component_id); + if (cmpntIdTag != null) { + cmpntId = cmpntIdTag; + } + Boolean isLiveLock = (Boolean) v.getTag(R.id.tag_key_live_lock_screen); + boolean isSamePkgButDifferentLockScreen = false; + Bundle params = null; + if (isLiveLock != null) { + params = new Bundle(); + params.putBoolean(IS_LIVE_LOCK_SCREEN_VIEW, isLiveLock); + + if (pkgName.equals(mSelectedComponentPkgName) && v != mPrevLockScreenView) { + isSamePkgButDifferentLockScreen = true; + } + mPrevLockScreenView = v; + } + if (DEBUG_SELECTOR) Toast.makeText(mContext, pkgName, Toast.LENGTH_SHORT).show(); + if (mListener != null && (isSamePkgButDifferentLockScreen || + !pkgName.equals(mSelectedComponentPkgName) || + pkgName.equals(EXTERNAL_WALLPAPER) || cmpntId != mSelectedComponentId)) { + mSelectedComponentPkgName = pkgName; + mSelectedComponentId = cmpntId; + mListener.onItemClicked(pkgName, cmpntId, params); + final int count = mContent.getChildCount(); + final Resources res = getResources(); + for (int i = 0; i < count; i++) { + final View child = mContent.getChildAt(i); + final TextView tv = (TextView) child.findViewById(R.id.title); + if (tv != null) { + if (child == v) { + tv.setTextColor( + res.getColor(R.color.component_selection_current_text_color)); + } else { + tv.setTextColor(res.getColor(android.R.color.white)); + } + } + } + } + } + }; + + private class ThemesObserver extends ContentObserver { + public ThemesObserver() { + super(null); + } + + public void register() { + mContext.getContentResolver().registerContentObserver( + ThemesColumns.CONTENT_URI, false, this); + } + + public void unregister() { + mContext.getContentResolver().unregisterContentObserver(this); + } + + @Override + public void onChange(boolean selfChange) { + // reload items by calling setComponentType() + if (mComponentType != null) { + final String componentType = mComponentType; + mComponentType = null; + mContent.post(new Runnable() { + @Override + public void run() { + setComponentType(componentType, mSelectedComponentPkgName); + } + }); + } + } + } + + public interface OnItemClickedListener { + public void onItemClicked(String pkgName, long componentId, Bundle params); + } + + public interface OnOpenCloseListener { + public void onSelectorOpened(); + public void onSelectorClosed(); + public void onSelectorClosing(); + } +} \ No newline at end of file diff --git a/src/org/cyanogenmod/theme/chooser/IconTransitionDrawable.java b/src/org/cyanogenmod/theme/chooser/IconTransitionDrawable.java new file mode 100644 index 0000000..96547f3 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/IconTransitionDrawable.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.os.SystemClock; + +/** + * An extension of LayerDrawables that is intended to cross-fade between + * the first and second layer. To start the transition, call {@link #startTransition(int)}. To + * display just the first layer, call {@link #resetTransition()}. + *

+ * It can be defined in an XML file with the <transition> element. + * Each Drawable in the transition is defined in a nested <item>. For more + * information, see the guide to Drawable Resources.

+ * + * @attr ref android.R.styleable#LayerDrawableItem_left + * @attr ref android.R.styleable#LayerDrawableItem_top + * @attr ref android.R.styleable#LayerDrawableItem_right + * @attr ref android.R.styleable#LayerDrawableItem_bottom + * @attr ref android.R.styleable#LayerDrawableItem_drawable + * @attr ref android.R.styleable#LayerDrawableItem_id + * + */ +public class IconTransitionDrawable extends LayerDrawable { + + /** + * A transition is about to start. + */ + private static final int TRANSITION_STARTING = 0; + + /** + * The transition has started and the animation is in progress + */ + private static final int TRANSITION_RUNNING = 1; + + /** + * No transition will be applied + */ + private static final int TRANSITION_NONE = 2; + + /** + * The current state of the transition. One of {@link #TRANSITION_STARTING}, + * {@link #TRANSITION_RUNNING} and {@link #TRANSITION_NONE} + */ + private int mTransitionState = TRANSITION_NONE; + + private long mStartTimeMillis; + private int mFrom; + private int mTo; + private int mDuration; + private int mAlpha = 0; + private float mFromScale; + private float mToScale; + + /** + * Create a new transition drawable with the specified list of layers. At least + * 2 layers are required for this drawable to work properly. + */ + public IconTransitionDrawable(Drawable[] layers) { + super(layers); + } + + /** + * Begin the second layer on top of the first layer. + * + * @param durationMillis The length of the transition in milliseconds + */ + public void startTransition(int durationMillis) { + mFrom = 0; + mTo = 255; + mAlpha = 0; + mFromScale = 0f; + mToScale = 1.0f; + mDuration = durationMillis; + mTransitionState = TRANSITION_STARTING; + invalidateSelf(); + } + + /** + * Show only the first layer. + */ + public void resetTransition() { + mAlpha = 0; + mTransitionState = TRANSITION_NONE; + invalidateSelf(); + } + + @Override + public void draw(Canvas canvas) { + boolean done = true; + float scale = 0f; + + switch (mTransitionState) { + case TRANSITION_STARTING: + mStartTimeMillis = SystemClock.uptimeMillis(); + done = false; + mTransitionState = TRANSITION_RUNNING; + break; + + case TRANSITION_RUNNING: + if (mStartTimeMillis >= 0) { + float normalized = (float) + (SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration; + done = normalized >= 1.0f; + normalized = Math.min(normalized, 1.0f); + mAlpha = (int) (mFrom + (mTo - mFrom) * normalized); + scale = mFromScale + (mToScale - mFromScale) * normalized; + } + break; + } + + final int alpha = mAlpha; + + if (done) { + // the setAlpha() calls below trigger invalidation and redraw. If we're done, just draw + // the appropriate drawable[s] and return + if (alpha == 0) { + getDrawable(0).draw(canvas); + } + if (alpha == 0xFF) { + getDrawable(1).draw(canvas); + + } + return; + } + + Drawable d; + d = getDrawable(0); + d.setAlpha(255 - alpha); + int cx = getIntrinsicWidth() / 2; + int cy = getIntrinsicHeight() / 2; + canvas.save(); + canvas.scale(1.0f - scale, 1.0f - scale, cx, cy); + d.draw(canvas); + canvas.restore(); + d.setAlpha(0xFF); + + if (alpha > 0) { + d = getDrawable(1); + d.setAlpha(alpha); + canvas.save(); + canvas.scale(scale, scale, cx, cy); + d.draw(canvas); + canvas.restore(); + d.setAlpha(0xFF); + } + + if (!done) { + invalidateSelf(); + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/MyThemeFragment.java b/src/org/cyanogenmod/theme/chooser/MyThemeFragment.java new file mode 100644 index 0000000..90a3f05 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/MyThemeFragment.java @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.res.Resources; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.MediaPlayer; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.content.Loader; +import android.util.Log; +import android.util.MutableLong; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import org.cyanogenmod.theme.util.AudioUtils; +import org.cyanogenmod.theme.util.CursorLoaderHelper; +import org.cyanogenmod.theme.util.PreferenceUtils; +import org.cyanogenmod.theme.util.ThemedTypefaceHelper; +import org.cyanogenmod.theme.util.TypefaceHelperCache; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.providers.ThemesContract; +import cyanogenmod.providers.ThemesContract.PreviewColumns; +import cyanogenmod.providers.ThemesContract.ThemesColumns; +import cyanogenmod.themes.ThemeChangeRequest; +import cyanogenmod.themes.ThemeChangeRequest.RequestType; +import cyanogenmod.themes.ThemeManager; + +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ALL; + +public class MyThemeFragment extends ThemeFragment { + private static final String TAG = MyThemeFragment.class.getSimpleName(); + + private static final String ARG_BASE_THEME_PACKAGE_NAME = "baseThemePkgName"; + private static final String ARG_BASE_THEME_NAME = "baseThemeName"; + private static final String ARG_BASE_THEME_AUTHOR = "baseThemeAuthor"; + + private String mBaseThemeName; + private String mBaseThemeAuthor; + + private SurfaceView mSurfaceView; + + static MyThemeFragment newInstance(String baseThemePkgName, String baseThemeName, + String baseThemeAuthor, boolean skipLoadingAnim, + boolean animateToLockScreenCard) { + MyThemeFragment f = new MyThemeFragment(); + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, CURRENTLY_APPLIED_THEME); + args.putString(ARG_BASE_THEME_PACKAGE_NAME, baseThemePkgName); + args.putString(ARG_BASE_THEME_NAME, baseThemeName); + args.putString(ARG_BASE_THEME_AUTHOR, baseThemeAuthor); + args.putBoolean(ARG_SKIP_LOADING_ANIM, skipLoadingAnim); + args.putBoolean(ARG_ANIMATE_TO_LOCK_SCREEN_CARD, animateToLockScreenCard); + f.setArguments(args); + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Context context = getActivity(); + ThemedTypefaceHelper helper = sTypefaceHelperCache.getHelperForTheme(context, + getAppliedFontPackageName()); + mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); + mBaseThemePkgName = getArguments().getString(ARG_BASE_THEME_PACKAGE_NAME); + mBaseThemeName = getArguments().getString(ARG_BASE_THEME_NAME); + mBaseThemeAuthor = getArguments().getString(ARG_BASE_THEME_AUTHOR); + mShowLockScreenSelectorAfterContentLoaded = getArguments().getBoolean( + ARG_ANIMATE_TO_LOCK_SCREEN_CARD); + mSurfaceView = createSurfaceView(); + populateBaseThemeSupportedComponents(mBaseThemePkgName); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = super.onCreateView(inflater, container, savedInstanceState); + mThemeTagLayout.setAppliedTagEnabled(true); + if (mBaseThemePkgName.equals(Utils.getDefaultThemePackageName(getActivity()))) { + mThemeTagLayout.setDefaultTagEnabled(true); + } + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { + mThemeTagLayout.setUpdatedTagEnabled(true); + } + mDelete.setVisibility(View.GONE); + setCustomized(isThemeCustomized()); + return v; + } + + @Override + public void onResume() { + super.onResume(); + if (!mExpanded && getLoaderManager().getLoader(0) != null) { + getLoaderManager().restartLoader(0, null, this); + } + + IntentFilter filter = new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED); + getActivity().registerReceiver(mWallpaperChangeReceiver, filter); + } + + @Override + public void onPause() { + getActivity().unregisterReceiver(mWallpaperChangeReceiver); + super.onPause(); + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (mThemeTagLayout == null) return; + + if (!isVisibleToUser) { + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { + mThemeTagLayout.setUpdatedTagEnabled(true); + } + } else { + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mBaseThemePkgName)) { + PreferenceUtils.removeUpdatedTheme(getActivity(), mBaseThemePkgName); + } + } + } + + @Override + protected boolean onPopupMenuItemClick(MenuItem item) { + switch(item.getItemId()) { + case R.id.menu_reset: + resetTheme(); + return true; + } + + return super.onPopupMenuItemClick(item); + } + + @Override + public void collapse(boolean applyTheme) { + super.collapse(applyTheme); + if (mSurfaceView != null) mSurfaceView.setVisibility(View.VISIBLE); + } + + @Override + public void expand() { + super.expand(); + if (mSurfaceView != null && mShadowFrame.indexOfChild(mSurfaceView) >= 0) { + mSurfaceView.setVisibility(View.GONE); + mWallpaper.setVisibility(View.INVISIBLE); + } + } + + @Override + public void performClick(boolean clickedOnContent) { + if (clickedOnContent) { + showCustomizeResetLayout(); + } else { + if (isShowingCustomizeResetLayout()) { + hideCustomizeResetLayout(); + } else { + super.performClick(clickedOnContent); + } + } + } + + @Override + public void setCurrentTheme(Map currentTheme, + MutableLong currentWallpaperComponentId) { + super.setCurrentTheme(currentTheme, currentWallpaperComponentId); + for (String key : currentTheme.keySet()) { + mSelectedComponentsMap.put(key, currentTheme.get(key)); + } + mSelectedWallpaperComponentId = currentWallpaperComponentId.value; + } + + @Override + public boolean componentsChanged() { + // If an external wallpaper/ls are set then something changed! + if (mExternalWallpaperUri != null || mExternalLockscreenUri != null) return true; + + for (String key : mSelectedComponentsMap.keySet()) { + String current = mCurrentTheme.get(key); + if (current == null || !current.equals(mSelectedComponentsMap.get(key))) { + return true; + } + if (ThemesColumns.MODIFIES_LAUNCHER.equals(key) && + mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { + return true; + } + } + return false; + } + + @Override + protected void applyThemeWhenPopulated(String pkgName, List components) { + super.applyThemeWhenPopulated(pkgName, components); + populateComponentsToApply(pkgName, components); + } + + private void populateComponentsToApply(String pkgName, List components) { + String selection = ThemesColumns.PKG_NAME + "=?"; + String[] selectionArgs = { pkgName }; + Cursor c = getActivity().getContentResolver().query(ThemesColumns.CONTENT_URI, + null, selection, selectionArgs, null); + if (c != null) { + if (c.getCount() > 0 && c.moveToFirst()) { + mSelectedComponentsMap.clear(); + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_ALARMS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_ALARMS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_BOOT_ANIM)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_BOOT_ANIM, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_FONTS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_FONTS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_ICONS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_ICONS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LAUNCHER)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LOCKSCREEN)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LOCKSCREEN, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_NAVIGATION_BAR)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_NAVIGATION_BAR, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_NOTIFICATIONS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_NOTIFICATIONS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_OVERLAYS)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_OVERLAYS, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_RINGTONES)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_RINGTONES, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_STATUS_BAR)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_STATUS_BAR, pkgName); + } + if (c.getInt(c.getColumnIndex(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) == 1) { + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, pkgName); + } + } + c.close(); + } + + // strip out any components that are not in the components list + if (components != null) { + Iterator> iterator = + mSelectedComponentsMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (!components.contains(entry.getKey())) { + iterator.remove(); + } + } + } + } + + private void loadComponentsToApply() { + for (String component : mSelectedComponentsMap.keySet()) { + loadComponentFromPackage(mSelectedComponentsMap.get(component), component, + mSelectedWallpaperComponentId); + } + } + + private BroadcastReceiver mWallpaperChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // only update if we are the current visible fragment or if there is no theme + // being applied. + ThemeManager tm = getThemeManager(); + if (!tm.isThemeApplying() || getUserVisibleHint()) { + final WallpaperManager wm = WallpaperManager.getInstance(context); + if (wm.getWallpaperInfo() != null) { + addSurfaceView(mSurfaceView); + } else { + removeSurfaceView(mSurfaceView); + } + + Drawable wp = context == null ? null : wm.getDrawable(); + if (wp != null) { + mWallpaper.setImageDrawable(wp); + mWallpaperCard.setWallpaper(wp); + } + } + } + }; + + private void setCustomized(boolean customized) { + mReset.setVisibility(customized ? View.VISIBLE : View.GONE); + mThemeTagLayout.setCustomizedTagEnabled(customized); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_ALL: + if (args != null) { + String pkgName = args.getString(ARG_PACKAGE_NAME); + if (pkgName != null) { + return super.onCreateLoader(id, args); + } + } + return CursorLoaderHelper.myThemeFragmentCursorLoader(getActivity(), id); + default: + // Only LOADER_ID_ALL differs for MyThemeFragment + return super.onCreateLoader(id, args); + } + } + + @Override + public void onLoadFinished(Loader loader, Cursor c) { + super.onLoadFinished(loader, c); + // if the theme is resetting, we need to apply these changes now that the supported + // theme components have been properly set. + if (loader.getId() == LOADER_ID_ALL) { + if (mThemeResetting) { + applyTheme(); + } else if (mApplyThemeOnPopulated) { + loadComponentsToApply(); + applyTheme(); + } else if (mSelectedComponentsMap.size() == 0) { + //Re-populates selected components with current theme. Why? + //We got here because the cursor was reloaded after the user pressed back and no + //changes were applied, causing the selected components map to be wiped out + mSelectedComponentsMap.putAll(mCurrentTheme); + } + } + } + + @Override + protected Map fillMissingComponentsWithDefault( + Map originalMap) { + // Only the ThemeFragment should be altering this, for the MyThemeFragment this is not + // desirable as it changes components the user did not even touch. + return originalMap; + } + + @Override + protected ThemeChangeRequest getThemeChangeRequestForComponents( + Map componentMap) { + return getThemeChangeRequestForComponents(componentMap, RequestType.USER_REQUEST_MIXNMATCH); + } + + @Override + protected Map getComponentsToApply() { + Map componentsToApply = mThemeResetting + ? getEmptyComponentsMap() + : new HashMap(); + if (mThemeResetting) { + final String pkgName = getThemePackageName(); + for (String component : mBaseThemeSupportedComponents) { + componentsToApply.put(component, pkgName); + } + } else { + // Only apply components that actually changed + for (String component : mSelectedComponentsMap.keySet()) { + String currentPkg = mCurrentTheme.get(component); + String selectedPkg = mSelectedComponentsMap.get(component); + if (currentPkg == null || mThemeResetting || !currentPkg.equals(selectedPkg) || + mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { + componentsToApply.put(component, selectedPkg); + } + } + if (mExternalLockscreenUri != null) { + if (mCurrentTheme.containsKey(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN)) { + componentsToApply.put(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); + } + if (mCurrentTheme.containsKey(ThemesColumns.MODIFIES_LOCKSCREEN)) { + componentsToApply.put(ThemesColumns.MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); + } + } + } + return componentsToApply; + } + + @Override + protected void populateSupportedComponents(Cursor c) { + } + + @Override + protected Boolean shouldShowComponentCard(String component) { + return true; + } + + @Override + protected void loadTitle(Cursor c) { + mTitle.setText(mBaseThemeName); + mAuthor.setText(mBaseThemeAuthor); + } + + @Override + protected void loadWallpaper(Cursor c, boolean animate) { + mExternalWallpaperUri = null; + int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + if (pkgNameIdx > -1) { + super.loadWallpaper(c, animate); + return; + } + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mWallpaperCard, true); + } + + int wpIdx = c.getColumnIndex(PreviewColumns.WALLPAPER_PREVIEW); + final Resources res = getResources(); + final Context context = getActivity(); + final WallpaperManager wm = WallpaperManager.getInstance(context); + if (wm.getWallpaperInfo() != null) { + addSurfaceView(mSurfaceView); + } else { + removeSurfaceView(mSurfaceView); + } + + Drawable wp = context == null ? null : wm.getDrawable(); + if (wp == null) { + Bitmap bmp = Utils.loadBitmapBlob(c, wpIdx); + if (bmp != null) wp = new BitmapDrawable(res, bmp); + } + if (wp != null) { + mWallpaper.setImageDrawable(wp); + mWallpaperCard.setWallpaper(wp); + setCardTitle(mWallpaperCard, mCurrentTheme.get(ThemesColumns.MODIFIES_LAUNCHER), + getString(R.string.wallpaper_label)); + } else { + mWallpaperCard.clearWallpaper(); + mWallpaperCard.setEmptyViewEnabled(true); + setAddComponentTitle(mWallpaperCard, getString(R.string.wallpaper_label)); + } + + if (animate) { + animateContentChange(R.id.wallpaper_card, mWallpaperCard, overlay); + } + } + + @Override + protected void loadLockScreen(Cursor c, boolean animate) { + mExternalLockscreenUri = null; + int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + if (pkgNameIdx > -1) { + super.loadLockScreen(c, animate); + return; + } + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mLockScreenCard, true); + } + + //If the current theme includes a lock wallpaper, the WallpaperMgr will + //return a valid Drawable we can display in the card. However, if the user + //picked a LLS, we need to get the path from the provider and manually load the bitmap + int wpIdx = c.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW); + Drawable wp = null; + if (wpIdx >= 0) { + final Resources res = getResources(); + Bitmap bmp = Utils.loadBitmapBlob(c, wpIdx); + if (bmp != null) wp = new BitmapDrawable(res, bmp); + } else { + final Context context = getActivity(); + wp = context == null ? null : + WallpaperManager.getInstance(context).getFastKeyguardDrawable(); + } + if (wp != null) { + mLockScreenCard.setWallpaper(wp); + } else if (!mSelectedComponentsMap.containsKey(ThemesColumns.MODIFIES_LOCKSCREEN)) { + mLockScreenCard.clearWallpaper(); + mLockScreenCard.setEmptyViewEnabled(true); + setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); + } + + if (animate) { + animateContentChange(R.id.lockscreen_card, mLockScreenCard, overlay); + } + } + + @Override + protected void loadFont(Cursor c, boolean animate) { + int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + if (pkgNameIdx > -1) { + super.loadFont(c, animate); + return; + } + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mFontPreview, true); + } + setCardTitle(mFontCard, mCurrentTheme.get(ThemesColumns.MODIFIES_FONTS), + getString(R.string.font_label)); + + TypefaceHelperCache cache = TypefaceHelperCache.getInstance(); + ThemedTypefaceHelper helper = cache.getHelperForTheme(getActivity(), + getAppliedFontPackageName()); + mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); + mFontPreview.setTypeface(mTypefaceNormal); + if (animate) { + animateContentChange(R.id.font_preview_container, mFontPreview, overlay); + } + } + + @Override + protected void loadAudible(int type, Cursor c, boolean animate) { + int pkgNameIdx = c.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME); + if (pkgNameIdx > -1) { + super.loadAudible(type, c, animate); + return; + } + ComponentCardView audibleContainer = null; + ImageView playPause = null; + String modsComponent = ""; + switch (type) { + case RingtoneManager.TYPE_RINGTONE: + audibleContainer = mRingtoneCard; + playPause = mRingtonePlayPause; + modsComponent = ThemesColumns.MODIFIES_RINGTONES; + break; + case RingtoneManager.TYPE_NOTIFICATION: + audibleContainer = mNotificationCard; + playPause = mNotificationPlayPause; + modsComponent = ThemesColumns.MODIFIES_NOTIFICATIONS; + break; + case RingtoneManager.TYPE_ALARM: + audibleContainer = mAlarmCard; + playPause = mAlarmPlayPause; + modsComponent = ThemesColumns.MODIFIES_ALARMS; + break; + } + if (audibleContainer == null) return; + + if (playPause == null) { + playPause = + (ImageView) audibleContainer.findViewById(R.id.play_pause); + } + TextView title = (TextView) audibleContainer.findViewById(R.id.audible_name); + MediaPlayer mp = mMediaPlayers.get(playPause); + if (mp == null) { + mp = new MediaPlayer(); + } + + final Context context = getActivity(); + Ringtone ringtone = null; + try { + Uri ringtoneUri = AudioUtils.loadDefaultAudible(context, type, mp); + if (ringtoneUri != null) ringtone = RingtoneManager.getRingtone(context, ringtoneUri); + } catch (IOException e) { + Log.w(TAG, "Unable to load default sound ", e); + } + + if (ringtone != null) { + title.setText(ringtone.getTitle(context)); + setCardTitle(audibleContainer, mCurrentTheme.get(modsComponent), + getAudibleLabel(type)); + } else { + title.setText(getString(R.string.audible_title_none)); + setAddComponentTitle(audibleContainer, getAudibleLabel(type)); + playPause.setVisibility(View.INVISIBLE); + audibleContainer.setEmptyViewEnabled(true); + } + + playPause.setTag(mp); + mMediaPlayers.put(playPause, mp); + playPause.setOnClickListener(mPlayPauseClickListener); + mp.setOnCompletionListener(mPlayCompletionListener); + } + + @Override + protected void loadStatusBar(Cursor c, boolean animate) { + super.loadStatusBar(c, animate); + setCardTitle(mStatusBarCard, mCurrentTheme.get(ThemesColumns.MODIFIES_STATUS_BAR), + getString(R.string.statusbar_label)); + } + + @Override + protected void loadIcons(Cursor c, boolean animate) { + super.loadIcons(c, animate); + setCardTitle(mIconCard, mCurrentTheme.get(ThemesColumns.MODIFIES_ICONS), + getString(R.string.icon_label)); + } + + @Override + protected void loadNavBar(Cursor c, boolean animate) { + super.loadNavBar(c, animate); + setCardTitle(mNavBarCard, mCurrentTheme.get(ThemesColumns.MODIFIES_NAVIGATION_BAR), + getString(R.string.navbar_label)); + } + + @Override + protected void loadStyle(Cursor c, boolean animate) { + super.loadStyle(c, animate); + setCardTitle(mStyleCard, mCurrentTheme.get(ThemesColumns.MODIFIES_OVERLAYS), + getString(R.string.style_label)); + } + + @Override + protected void loadBootAnimation(Cursor c) { + super.loadBootAnimation(c); + setCardTitle(mBootAnimationCard, mCurrentTheme.get(ThemesColumns.MODIFIES_BOOT_ANIM), + getString(R.string.boot_animation_label)); + } + + @Override + public String getThemePackageName() { + if (mBaseThemePkgName == null) { + // check if the package name is defined in the arguments bundle + Bundle bundle = getArguments(); + if (bundle != null) { + mBaseThemePkgName = bundle.getString(ARG_BASE_THEME_PACKAGE_NAME); + } + } + return mBaseThemePkgName; + } + + private SurfaceView createSurfaceView() { + final Context context = getActivity(); + if (context == null) return null; + + SurfaceView sv = new SurfaceView(context); + final Resources res = context.getResources(); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( + res.getDimensionPixelSize(R.dimen.wallpaper_preview_width), + res.getDimensionPixelSize(R.dimen.theme_preview_height), + Gravity.CENTER_HORIZONTAL); + sv.setLayoutParams(params); + + return sv; + } + + private void addSurfaceView(SurfaceView sv) { + if (mShadowFrame.indexOfChild(mSurfaceView) < 0) { + int idx = mShadowFrame.indexOfChild(mWallpaper); + mShadowFrame.addView(sv, idx + 1); + } + } + + private void removeSurfaceView(SurfaceView sv) { + if (mShadowFrame.indexOfChild(mSurfaceView) >= 0) { + mShadowFrame.removeView(sv); + } + } + + /** + * Populates mBaseThemeSupportedComponents. + * @param pkgName Package name of the base theme used + */ + private void populateBaseThemeSupportedComponents(String pkgName) { + String selection = ThemesColumns.PKG_NAME + "=?"; + String[] selectionArgs = { pkgName }; + Cursor c = getActivity().getContentResolver().query(ThemesColumns.CONTENT_URI, + null, selection, selectionArgs, null); + if (c != null) { + if (c.moveToFirst()) { + List components = ThemeUtils.getAllComponents(); + final String baseThemePackageName = getThemePackageName(); + for (String component : components) { + int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int modifiesCompIdx = c.getColumnIndex(component); + + String pkg = pkgIdx >= 0 ? c.getString(pkgIdx) : null; + boolean supported = (modifiesCompIdx >= 0) && (c.getInt(modifiesCompIdx) == 1); + if (supported && baseThemePackageName.equals(pkg)) { + mBaseThemeSupportedComponents.add(component); + } + } + } + c.close(); + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/NewFragmentStatePagerAdapter.java b/src/org/cyanogenmod/theme/chooser/NewFragmentStatePagerAdapter.java new file mode 100644 index 0000000..cefc344 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/NewFragmentStatePagerAdapter.java @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.os.Bundle; +import android.os.Parcelable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.view.PagerAdapter; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Implementation of {@link android.support.v4.view.PagerAdapter} that + * uses a {@link android.support.v4.app.Fragment} to manage each page. This class also handles + * saving and restoring of fragment's state. + * + *

This version of the pager is more useful when there are a large number + * of pages, working more like a list view. When pages are not visible to + * the user, their entire fragment may be destroyed, only keeping the saved + * state of that fragment. This allows the pager to hold on to much less + * memory associated with each visited page as compared to + * {@link android.support.v4.app.FragmentPagerAdapter} at the cost of potentially more overhead when + * switching between pages. + * + *

When using FragmentPagerAdapter the host ViewPager must have a + * valid ID set.

+ * + *

Subclasses only need to implement {@link #getItem(int)} + * and {@link #getCount()} to have a working adapter. + * + *

Here is an example implementation of a pager containing fragments of + * lists: + * + * {@sample development/samples/Support13Demos/src/com/example/android/supportv13/app/FragmentStatePagerSupport.java + * complete} + * + *

The R.layout.fragment_pager resource of the top-level fragment is: + * + * {@sample development/samples/Support13Demos/res/layout/fragment_pager.xml + * complete} + * + *

The R.layout.fragment_pager_list resource containing each + * individual fragment's layout is: + * + * {@sample development/samples/Support13Demos/res/layout/fragment_pager_list.xml + * complete} + */ +public abstract class NewFragmentStatePagerAdapter extends PagerAdapter { + private static final String TAG = NewFragmentStatePagerAdapter.class.getSimpleName(); + private static final boolean DEBUG = false; + + private final FragmentManager mFragmentManager; + private FragmentTransaction mCurTransaction = null; + + private long[] mItemIds = new long[] {}; + private ArrayList mSavedState = new ArrayList(); + private ArrayList mFragments = new ArrayList(); + private Fragment mCurrentPrimaryItem = null; + + public NewFragmentStatePagerAdapter(FragmentManager fm) { + + mFragmentManager = fm; + createIdCache(); + } + + /** + * Return the Fragment associated with a specified position. + */ + public abstract Fragment getItem(int position); + + /** + * Return a unique identifier for the item at the given position. + */ + public abstract long getItemId(int position); + + @Override + public void startUpdate(ViewGroup container) { + } + + private void checkForIdChanges() { + long[] newItemIds = new long[getCount()]; + for (int i = 0; i < newItemIds.length; i++) { + newItemIds[i] = getItemId(i); + } + + if (!Arrays.equals(mItemIds, newItemIds)) { + ArrayList newSavedState = new ArrayList(); + ArrayList newFragments = new ArrayList(); + + for (int oldPosition = 0; oldPosition < mItemIds.length; oldPosition++) { + int newPosition = POSITION_NONE; + for (int i = 0; i < newItemIds.length; i++) { + if (mItemIds[oldPosition] == newItemIds[i]) { + newPosition = i; + break; + } + } + if (newPosition >= 0) { + if (oldPosition < mSavedState.size()) { + Fragment.SavedState savedState = mSavedState.get(oldPosition); + if (savedState != null) { + while (newSavedState.size() <= newPosition) { + newSavedState.add(null); + } + newSavedState.set(newPosition, savedState); + } + } + if (oldPosition < mFragments.size()) { + Fragment fragment = mFragments.get(oldPosition); + if (fragment != null) { + while (newFragments.size() <= newPosition) { + newFragments.add(null); + } + newFragments.set(newPosition, fragment); + } + } + } + } + + mItemIds = newItemIds; + mSavedState = newSavedState; + mFragments = newFragments; + } + } + + @Override + public void notifyDataSetChanged() { + checkForIdChanges(); + + super.notifyDataSetChanged(); + } + + /** + * Create the initial set of item IDs. Run this after you have set your adapter data. + */ + public void createIdCache() { + // If we have already stored ids, don't overwrite them + if (mItemIds.length == 0) { + // getCount might have overhead, so run it as late as possible + final int count = getCount(); + if (count > 0) { + mItemIds = new long[count]; + for (int i = 0; i < count; i++) { + mItemIds[i] = getItemId(i); + } + } + } + } + + @Override + public Object instantiateItem(ViewGroup container, int position) { + + createIdCache(); + + // If we already have this item instantiated, there is nothing + // to do. This can happen when we are restoring the entire pager + // from its saved state, where the fragment manager has already + // taken care of restoring the fragments we previously had instantiated. + if (mFragments.size() > position) { + Fragment f = mFragments.get(position); + if (f != null) { + return f; + } + } + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + + Fragment fragment = getItem(position); + if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); + if (mSavedState.size() > position) { + Fragment.SavedState fss = mSavedState.get(position); + if (fss != null) { + fragment.setInitialSavedState(fss); + } + } + while (mFragments.size() <= position) { + mFragments.add(null); + } + fragment.setMenuVisibility(false); + fragment.setUserVisibleHint(false); + mFragments.set(position, fragment); + mCurTransaction.add(container.getId(), fragment); + + return fragment; + } + + @Override + public void destroyItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment)object; + + if (mCurTransaction == null) { + mCurTransaction = mFragmentManager.beginTransaction(); + } + if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + + " v=" + ((Fragment)object).getView()); + while (mSavedState.size() <= position) { + mSavedState.add(null); + } + mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment)); + + // Only set the position to null if the fragment being removed is at "position" + // We do this because checkForIdChanges updates the mFragments list and if a fragment + // was removed then the fragment at "position" is not the fragment that was removed. + if (position < mFragments.size() && fragment == mFragments.get(position)) { + mFragments.set(position, null); + } + mCurTransaction.remove(fragment); + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment)object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + mCurrentPrimaryItem.setUserVisibleHint(false); + } + if (fragment != null) { + fragment.setMenuVisibility(true); + fragment.setUserVisibleHint(true); + } + mCurrentPrimaryItem = fragment; + } + } + + @Override + public void finishUpdate(ViewGroup container) { + if (mCurTransaction != null) { + mCurTransaction.commitAllowingStateLoss(); + mCurTransaction = null; + mFragmentManager.executePendingTransactions(); + } + } + + @Override + public boolean isViewFromObject(View view, Object object) { + return ((Fragment)object).getView() == view; + } + + @Override + public Parcelable saveState() { + Bundle state = null; + + mItemIds = new long[getCount()]; + for (int i = 0; i < mItemIds.length; i++) { + mItemIds[i] = getItemId(i); + } + if (mSavedState.size() > 0) { + state = new Bundle(); + + if (mItemIds.length > 0) { + state.putLongArray("itemids", mItemIds); + } + + Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()]; + mSavedState.toArray(fss); + state.putParcelableArray("states", fss); + } + for (int i=0; i keys = bundle.keySet(); + for (String key: keys) { + if (key.startsWith("f")) { + int index = Integer.parseInt(key.substring(1)); + Fragment f = mFragmentManager.getFragment(bundle, key); + if (f != null) { + while (mFragments.size() <= index) { + mFragments.add(null); + } + f.setMenuVisibility(false); + mFragments.set(index, f); + } else { + Log.w(TAG, "Bad fragment at key " + key); + } + } + } + checkForIdChanges(); + } + } +} \ No newline at end of file diff --git a/src/org/cyanogenmod/theme/chooser/NotificationHijackingService.java b/src/org/cyanogenmod/theme/chooser/NotificationHijackingService.java new file mode 100644 index 0000000..dddd8f5 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/NotificationHijackingService.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.provider.Settings; +import android.service.notification.NotificationListenerService; +import android.service.notification.StatusBarNotification; +import android.text.TextUtils; + +public class NotificationHijackingService extends NotificationListenerService { + private static final String TAG = NotificationHijackingService.class.getName(); + private static final String GOOGLE_PLAY_PACKAGE_NAME = "com.android.vending"; + private static final String ACTION_INSTALLED = + "com.android.vending.SUCCESSFULLY_INSTALLED_CLICKED"; + private static final String EXTRA_PACKAGE_NAME = "package_name"; + + @Override + public void onNotificationPosted(StatusBarNotification sbn) { + if (GOOGLE_PLAY_PACKAGE_NAME.equals(sbn.getPackageName())) { + PendingIntent contentIntent = sbn.getNotification().contentIntent; + if (contentIntent == null) return; + Intent intent = contentIntent.getIntent(); + if (intent == null) return; + String action = intent.getAction(); + if (ACTION_INSTALLED.equals(action)) { + String pkgName = intent.getStringExtra(EXTRA_PACKAGE_NAME); + try { + PackageInfo pi = getPackageManager().getPackageInfo(pkgName, 0); + if (pi != null) { + if (pi.themeInfo != null) { + cancelNotification(sbn.getKey()); + } + } + } catch (PackageManager.NameNotFoundException e) { + } + } + } + } + + @Override + public void onNotificationRemoved(StatusBarNotification sbn) { + } + + // ensure that this notification listener is enabled. + // the service watches for google play notifications + public static void ensureEnabled(Context context) { + ComponentName me = new ComponentName(context, NotificationHijackingService.class); + String meFlattened = me.flattenToString(); + + String existingListeners = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); + + if (!TextUtils.isEmpty(existingListeners)) { + if (existingListeners.contains(meFlattened)) { + return; + } else { + existingListeners += ":" + meFlattened; + } + } else { + existingListeners = meFlattened; + } + + Settings.Secure.putString(context.getContentResolver(), + Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, + existingListeners); + } +} diff --git a/src/org/cyanogenmod/theme/chooser/PagerContainer.java b/src/org/cyanogenmod/theme/chooser/PagerContainer.java new file mode 100644 index 0000000..852b847 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/PagerContainer.java @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.content.Context; +import android.graphics.Point; +import android.support.v4.view.ThemeViewPager; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +/** + * PagerContainer: A layout that displays a ViewPager with its children that are outside + * the typical pager bounds. + */ +public class PagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener { + private static final int ANIMATE_OUT_DURATION = 300; + private static final int ANIMATE_OUT_INTERPOLATE_FACTOR = 1; + private static final int ANIMATE_IN_DURATION = 300; + private static final int ANIMATE_IN_INTERPOLATE_FACTOR = 2; + + private ThemeViewPager mPager; + private Point mCenter = new Point(); + private Point mInitialTouch = new Point(); + private int mCollapsedHeight; + private boolean mIsAnimating = false; + + boolean mNeedsRedraw = false; + + public PagerContainer(Context context) { + this(context, null, 0); + } + + public PagerContainer(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PagerContainer(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + mCollapsedHeight = generateLayoutParams(attrs).height; + + //Disable clipping of children so non-selected pages are visible + setClipChildren(false); + } + + @Override + protected void onFinishInflate() { + try { + mPager = (ThemeViewPager) getChildAt(0); + mPager.setOnPageChangeListener(this); + } catch (Exception e) { + throw new IllegalStateException("The root child of PagerContainer must be a ViewPager"); + } + } + + public ThemeViewPager getViewPager() { + return mPager; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + mCenter.x = w / 2; + mCenter.y = h / 2; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mIsAnimating) return true; + return super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + // Do not allow touch events to propagate if we are animating + if (mIsAnimating) return true; + + //We capture any touches not already handled by the ViewPager + // to implement scrolling from a touch outside the pager bounds. + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mInitialTouch.x = (int)ev.getX(); + mInitialTouch.y = (int)ev.getY(); + default: + ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y); + break; + } + + return mPager.dispatchTouchEvent(ev); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + //Force the container to redraw on scrolling. + //Without this the outer pages render initially and then stay static + if (mNeedsRedraw) invalidate(); + } + + @Override + public void onPageSelected(int position) { } + + @Override + public void onPageScrollStateChanged(int state) { + mNeedsRedraw = (state != ThemeViewPager.SCROLL_STATE_IDLE); + } + + public void setIsAnimating(boolean isAnimating) { + mIsAnimating = isAnimating; + } + + public void expand() { + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); + params.height = LinearLayout.LayoutParams.MATCH_PARENT; + setLayoutParams(params); + + mPager.setExpanded(true); + + final int current = mPager.getCurrentItem(); + final int prevY = (int) getY(); + + //Since our viewpager's width is changing to fill the screen + //we must start the left/right children of the current page inwards on first draw + final int lChildPrevXf; + final int rChildPrevXf; + + if (current != 0) { + final View lchild = mPager.getViewForPosition(current - 1); + lChildPrevXf = (int) lchild.getX(); + } else { + lChildPrevXf = 0; + } + + if (current < mPager.getAdapter().getCount() - 1) { + View rchild = mPager.getViewForPosition(current + 1); + rChildPrevXf = (int) rchild.getX(); + } else { + rChildPrevXf = 0; + } + + + final ViewTreeObserver observer = mPager.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + if (current != 0) { + View lchild = mPager.getViewForPosition(current - 1); + lchild.setTranslationY(prevY - getY()); + lchild.setX(lChildPrevXf); + animateChildOut(lchild, -getWidth()); + } + + if (current < mPager.getAdapter().getCount() - 1) { + View rchild = mPager.getViewForPosition(current + 1); + rchild.setX(rChildPrevXf); + rchild.setTranslationY(prevY - getY()); + animateChildOut(rchild, getWidth()); + } + return false; + } + }); + } + + public void collapse() { + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(getLayoutParams()); + params.height = mCollapsedHeight; + setLayoutParams(params); + + mPager.setExpanded(false); + int current = mPager.getCurrentItem(); + final int prevY = (int) getY(); + + if (current != 0) { + View lchild = mPager.getViewForPosition(current - 1); + lchild.setTranslationY(0); + animateChildIn(lchild); + } + + if (current < mPager.getAdapter().getCount() - 1) { + View rchild = mPager.getViewForPosition(current + 1); + rchild.setTranslationY(0); + animateChildIn(rchild); + } + } + + private void animateChildOut(final View v, float endX) { + v.animate() + .translationX(endX) + .setDuration(ANIMATE_OUT_DURATION) + .setInterpolator(new AccelerateInterpolator(ANIMATE_OUT_INTERPOLATE_FACTOR)); + } + + private void animateChildIn(final View v) { + v.animate() + .translationX(0) + .setDuration(ANIMATE_IN_DURATION) + .setInterpolator(new DecelerateInterpolator(ANIMATE_IN_INTERPOLATE_FACTOR)); + } +} \ No newline at end of file diff --git a/src/org/cyanogenmod/theme/chooser/ThemeFragment.java b/src/org/cyanogenmod/theme/chooser/ThemeFragment.java new file mode 100644 index 0000000..d0891a7 --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/ThemeFragment.java @@ -0,0 +1,3059 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.IntEvaluator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.WallpaperManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Color; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.os.FileUtils; +import android.os.Handler; +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.Loader; +import android.text.TextUtils; +import android.util.Log; +import android.util.MutableLong; +import android.util.SparseArray; +import android.view.Display; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; +import android.view.ViewTreeObserver; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.ScaleAnimation; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.PopupMenu; +import android.widget.ProgressBar; +import android.widget.Space; +import android.widget.TextView; +import android.widget.Toast; + +import org.cyanogenmod.theme.chooser.ComponentSelector.OnItemClickedListener; +import org.cyanogenmod.theme.util.AudioUtils; +import org.cyanogenmod.theme.util.BootAnimationHelper; +import org.cyanogenmod.theme.util.CursorLoaderHelper; +import org.cyanogenmod.theme.util.IconPreviewHelper; +import org.cyanogenmod.theme.util.PreferenceUtils; +import org.cyanogenmod.theme.util.ThemedTypefaceHelper; +import org.cyanogenmod.theme.util.TypefaceHelperCache; +import org.cyanogenmod.theme.util.Utils; +import org.cyanogenmod.theme.util.WallpaperUtils; +import org.cyanogenmod.theme.widget.BootAniImageView; +import org.cyanogenmod.theme.widget.ConfirmCancelOverlay; +import org.cyanogenmod.theme.widget.LockableScrollView; +import org.cyanogenmod.theme.widget.ThemeTagLayout; + +import cyanogenmod.app.ThemeVersion; +import cyanogenmod.providers.CMSettings; +import cyanogenmod.providers.ThemesContract.PreviewColumns; +import cyanogenmod.providers.ThemesContract.ThemesColumns; +import cyanogenmod.themes.ThemeChangeRequest; +import cyanogenmod.themes.ThemeChangeRequest.RequestType; +import cyanogenmod.themes.ThemeManager; + +import org.cyanogenmod.internal.util.CmLockPatternUtils; +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.InvalidParameterException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.zip.ZipFile; + +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; + +import static org.cyanogenmod.theme.chooser.ComponentSelector.DEFAULT_COMPONENT_ID; + +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_INVALID; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ALL; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_STATUS_BAR; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_FONT; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ICONS; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_WALLPAPER; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_NAVIGATION_BAR; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_LOCKSCREEN; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_STYLE; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_BOOT_ANIMATION; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_RINGTONE; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_NOTIFICATION; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_ALARM; +import static org.cyanogenmod.theme.util.CursorLoaderHelper.LOADER_ID_LIVE_LOCK_SCREEN; + +import static cyanogenmod.providers.CMSettings.Secure.LIVE_LOCK_SCREEN_ENABLED; + +import static org.cyanogenmod.internal.util.ThemeUtils.SYSTEM_TARGET_API; + +public class ThemeFragment extends Fragment implements LoaderManager.LoaderCallbacks, + ThemeManager.ThemeChangeListener, ThemeManager.ThemeProcessingListener { + private static final String TAG = ThemeFragment.class.getSimpleName(); + + public static final int ANIMATE_START_DELAY = 200; + public static final int ANIMATE_DURATION = 300; + public static final int ANIMATE_INTERPOLATE_FACTOR = 3; + public static final int ANIMATE_COMPONENT_CHANGE_DURATION = 200; + public static final int ANIMATE_COMPONENT_ICON_DELAY = 50; + public static final int ANIMATE_PROGRESS_IN_DURATION = 500; + public static final int ANIMATE_TITLE_OUT_DURATION = 400; + public static final int ANIMATE_PROGRESS_OUT_DURATION = 400; + public static final int ANIMATE_TITLE_IN_DURATION = 500; + public static final int ANIMATE_APPLY_LAYOUT_DURATION = 300; + + public static final String CURRENTLY_APPLIED_THEME = "currently_applied_theme"; + + private static final ComponentName COMPONENT_DIALER = + new ComponentName("com.android.dialer", "com.android.dialer.DialtactsActivity"); + private static final ComponentName COMPONENT_DIALERNEXT = + new ComponentName("com.cyngn.dialer", "com.android.dialer.DialtactsActivity"); + private static final ComponentName COMPONENT_MESSAGING = + new ComponentName("com.android.mms", "com.android.mms.ui.ConversationList"); + private static final ComponentName COMPONENT_CAMERANEXT = + new ComponentName("com.cyngn.cameranext", "com.android.camera.CameraLauncher"); + private static final ComponentName COMPONENT_CAMERA = + new ComponentName("com.android.camera2", "com.android.camera.CameraActivity"); + private static final ComponentName COMPONENT_BROWSER = + new ComponentName("com.android.browser", "com.android.browser.BrowserActivity"); + private static final ComponentName COMPONENT_SETTINGS = + new ComponentName("com.android.settings", "com.android.settings.Settings"); + private static final ComponentName COMPONENT_CALENDAR = + new ComponentName("com.android.calendar", "com.android.calendar.AllInOneActivity"); + private static final ComponentName COMPONENT_GALERY = + new ComponentName("com.android.gallery3d", "com.android.gallery3d.app.GalleryActivity"); + + private static final String CAMERA_NEXT_PACKAGE = "com.cyngn.cameranext"; + private static final String DIALER_NEXT_PACKAGE = "com.cyngn.dialer"; + + private static final int ADDITIONAL_CONTENT_SPACE_ID = 123456; + private static final long SLIDE_CONTENT_ANIM_DURATION = 300L; + private static final long LOCK_SCREEN_CARD_SCROLL_ANIMATION_DURATION = 400; + private static final long SHOW_LOCK_SCREEN_CARD_DELAY = 500; + + private static final int DEFAULT_WIFI_MARGIN = 0; + private static final int DEFAULT_CLOCK_COLOR = Color.WHITE; + + protected static final String WALLPAPER_NONE = ""; + protected static final String LOCKSCREEN_NONE = ""; + + protected static final String ARG_PACKAGE_NAME = "pkgName"; + protected static final String ARG_COMPONENT_ID = "cmpntId"; + protected static final String ARG_SKIP_LOADING_ANIM = "skipLoadingAnim"; + protected static final String ARG_ANIMATE_TO_LOCK_SCREEN_CARD = "animateToLockScreenCard"; + + private static final String LLS_PACKAGE_NAME = "com.cyngn.lockscreen.live"; + private static final String LLS_PROVIDER_NAME = + "com.cyngn.lockscreen.live.LockScreenProviderService"; + + private static final int PERMISSION_REQUEST = 100; + + protected static ComponentName[] sIconComponents; + + protected static TypefaceHelperCache sTypefaceHelperCache; + + /** + * Maps the card's resource ID to a theme component + */ + private final SparseArray mCardIdsToComponentTypes = new SparseArray(); + + protected String mPkgName; + protected Typeface mTypefaceNormal; + protected int mBatteryStyle; + + protected LockableScrollView mScrollView; + protected ViewGroup mScrollContent; + protected ViewGroup mPreviewContent; // Contains icons, font, nav/status etc. Not wallpaper + protected View mLoadingView; + + //Status Bar Views + protected ComponentCardView mStatusBarCard; + protected ImageView mBluetooth; + protected ImageView mWifi; + protected ImageView mSignal; + protected ImageView mBattery; + protected TextView mClock; + + // Other Misc Preview Views + protected FrameLayout mShadowFrame; + protected ImageView mWallpaper; + protected ViewGroup mStatusBar; + protected TextView mFontPreview; + protected ComponentCardView mStyleCard; + protected ComponentCardView mFontCard; + protected ComponentCardView mIconCard; + protected ComponentCardView mBootAnimationCard; + protected BootAniImageView mBootAnimation; + + // Nav Bar Views + protected ComponentCardView mNavBarCard; + protected ViewGroup mNavBar; + protected ImageView mBackButton; + protected ImageView mHomeButton; + protected ImageView mRecentButton; + + // Title Card Views + protected ViewGroup mTitleCard; + protected ViewGroup mTitleLayout; + protected TextView mTitle; + protected TextView mAuthor; + protected ImageView mCustomize; + protected ImageView mOverflow; + protected ImageView mDelete; + protected ImageView mReset; + protected ProgressBar mProgress; + + // Additional Card Views + protected LinearLayout mAdditionalCards; + protected WallpaperCardView mWallpaperCard; + protected WallpaperCardView mLockScreenCard; + + // Style views + protected ImageView mStylePreview; + + // Sound cards + protected ComponentCardView mRingtoneCard; + protected ImageView mRingtonePlayPause; + protected ComponentCardView mNotificationCard; + protected ImageView mNotificationPlayPause; + protected ComponentCardView mAlarmCard; + protected ImageView mAlarmPlayPause; + protected Map mMediaPlayers; + + protected Handler mHandler; + + protected int mActiveCardId = -1; + protected ComponentSelector mSelector; + // Supported components for the theme this fragment represents + protected Map mSelectedComponentsMap = new HashMap(); + protected Long mSelectedWallpaperComponentId; + // Current system theme configuration as component -> pkgName + protected Map mCurrentTheme = new HashMap(); + protected MutableLong mCurrentWallpaperComponentId = new MutableLong(DEFAULT_COMPONENT_ID); + // Set of components available in the base theme + protected HashSet mBaseThemeSupportedComponents = new HashSet(); + protected Cursor mCurrentCursor; + protected int mCurrentLoaderId; + protected boolean mThemeResetting; + protected boolean mSkipLoadingAnim; + + // Accept/Cancel overlay + protected ConfirmCancelOverlay mConfirmCancelOverlay; + + // Customize/Reset theme layout + protected View mCustomizeResetLayout; + protected View mResetButton; + protected View mCustomizeButton; + protected View mDismissButton; + + // Processing theme layout + protected View mProcessingThemeLayout; + + protected ThemeTagLayout mThemeTagLayout; + + protected View mClickableView; + protected String mBaseThemePkgName; + + protected Uri mExternalWallpaperUri; + protected Uri mExternalLockscreenUri; + + protected boolean mExpanded; + protected boolean mProcessingResources; + protected boolean mApplyThemeOnPopulated; + + protected boolean mIsLegacyTheme; + + private Runnable mAfterPermissionGrantedRunnable; + + private static final int mThemeVersion = ThemeVersion.getVersion(); + + protected boolean mShowLockScreenSelectorAfterContentLoaded; + + protected enum CustomizeResetAction { + Customize, + Reset, + Dismiss + } + + static ThemeFragment newInstance(String pkgName, boolean skipLoadingAnim) { + ThemeFragment f = new ThemeFragment(); + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, pkgName); + args.putBoolean(ARG_SKIP_LOADING_ANIM, skipLoadingAnim); + args.putLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); + f.setArguments(args); + return f; + } + + /** + * When creating, retrieve this instance's number from its arguments. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Context context = getActivity(); + mPkgName = getArguments().getString(ARG_PACKAGE_NAME); + mSkipLoadingAnim = getArguments().getBoolean(ARG_SKIP_LOADING_ANIM); + // TODO: Load from settings once available + mBatteryStyle = 0;/*Settings.System.getInt(context.getContentResolver(), + Settings.System.STATUS_BAR_BATTERY_STYLE, 0);*/ + + getIconComponents(context); + if (sTypefaceHelperCache == null) { + sTypefaceHelperCache = TypefaceHelperCache.getInstance(); + } + ThemedTypefaceHelper helper = sTypefaceHelperCache.getHelperForTheme(context, mPkgName); + mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); + + mHandler = new Handler(); + + mCardIdsToComponentTypes.put(R.id.status_bar_container, MODIFIES_STATUS_BAR); + mCardIdsToComponentTypes.put(R.id.font_preview_container, MODIFIES_FONTS); + mCardIdsToComponentTypes.put(R.id.icon_container, MODIFIES_ICONS); + mCardIdsToComponentTypes.put(R.id.navigation_bar_container, MODIFIES_NAVIGATION_BAR); + mCardIdsToComponentTypes.put(R.id.wallpaper_card, MODIFIES_LAUNCHER); + mCardIdsToComponentTypes.put(R.id.lockscreen_card, MODIFIES_LOCKSCREEN); + mCardIdsToComponentTypes.put(R.id.style_card, MODIFIES_OVERLAYS); + mCardIdsToComponentTypes.put(R.id.bootani_preview_container, MODIFIES_BOOT_ANIM); + mCardIdsToComponentTypes.put(R.id.ringtone_preview_container, MODIFIES_RINGTONES); + mCardIdsToComponentTypes.put(R.id.notification_preview_container, MODIFIES_NOTIFICATIONS); + mCardIdsToComponentTypes.put(R.id.alarm_preview_container, MODIFIES_ALARMS); + + mMediaPlayers = new HashMap(3); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.fragment_pager_list, container, false); + + mScrollView = (LockableScrollView) v.findViewById(android.R.id.list); + mScrollView.setScrollingEnabled(false); + mScrollContent = (ViewGroup) mScrollView.getChildAt(0); + mPreviewContent = (ViewGroup) v.findViewById(R.id.preview_container); + mLoadingView = v.findViewById(R.id.loading_view); + mThemeTagLayout = (ThemeTagLayout) v.findViewById(R.id.tag_layout); + + // Status Bar + mStatusBarCard = (ComponentCardView) v.findViewById(R.id.status_bar_container); + mStatusBar = (ViewGroup) v.findViewById(R.id.status_bar); + mBluetooth = (ImageView) v.findViewById(R.id.bluetooth_icon); + mWifi = (ImageView) v.findViewById(R.id.wifi_icon); + mSignal = (ImageView) v.findViewById(R.id.signal_icon); + mBattery = (ImageView) v.findViewById(R.id.battery); + mClock = (TextView) v.findViewById(R.id.clock); + + // Wallpaper / Font / Icons / etc + mWallpaper = (ImageView) v.findViewById(R.id.wallpaper); + mFontCard = (ComponentCardView) v.findViewById(R.id.font_preview_container); + mFontPreview = (TextView) v.findViewById(R.id.font_preview); + mFontPreview.setTypeface(mTypefaceNormal); + mIconCard = (ComponentCardView) v.findViewById(R.id.icon_container); + mShadowFrame = (FrameLayout) v.findViewById(R.id.shadow_frame); + mStyleCard = (ComponentCardView) v.findViewById(R.id.style_card); + mStylePreview = (ImageView) v.findViewById(R.id.style_preview); + mBootAnimationCard = (ComponentCardView) v.findViewById(R.id.bootani_preview_container); + mBootAnimation = + (BootAniImageView) mBootAnimationCard.findViewById(R.id.bootani_preview); + mRingtoneCard = (ComponentCardView) v.findViewById(R.id.ringtone_preview_container); + mRingtonePlayPause = (ImageView) mRingtoneCard.findViewById(R.id.play_pause); + mNotificationCard = (ComponentCardView) v.findViewById(R.id.notification_preview_container); + mNotificationPlayPause = (ImageView) mNotificationCard.findViewById(R.id.play_pause); + mAlarmCard = (ComponentCardView) v.findViewById(R.id.alarm_preview_container); + mAlarmPlayPause = (ImageView) mAlarmCard.findViewById(R.id.play_pause); + + // Nav Bar + mNavBarCard = (ComponentCardView) v.findViewById(R.id.navigation_bar_container); + mNavBar = (ViewGroup) v.findViewById(R.id.navigation_bar); + mBackButton = (ImageView) v.findViewById(R.id.back_button); + mHomeButton = (ImageView) v.findViewById(R.id.home_button); + mRecentButton = (ImageView) v.findViewById(R.id.recent_button); + + // Title Card + mTitleCard = (ViewGroup)v.findViewById(R.id.title_card); + mTitleLayout = (ViewGroup) v.findViewById(R.id.title_layout); + mTitle = (TextView) v.findViewById(R.id.title); + mAuthor = (TextView) v.findViewById(R.id.author); + mProgress = (ProgressBar) v.findViewById(R.id.apply_progress); + mOverflow = (ImageView) v.findViewById(R.id.overflow); + mOverflow.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (isShowingConfirmCancelOverlay()) { + hideConfirmCancelOverlay(); + } else if (isShowingCustomizeResetLayout()) { + hideCustomizeResetLayout(); + } + + PopupMenu popupmenu = new PopupMenu(getActivity(), mTitleCard, Gravity.END); + popupmenu.getMenuInflater().inflate(R.menu.overflow, popupmenu.getMenu()); + + Menu menu = popupmenu.getMenu(); + if (CURRENTLY_APPLIED_THEME.equals(mPkgName) || + mPkgName.equals(Utils.getDefaultThemePackageName(getActivity())) || + mPkgName.equals(ThemeConfig.SYSTEM_DEFAULT)) { + menu.findItem(R.id.menu_delete).setEnabled(false); + } + if (!mThemeTagLayout.isCustomizedTagEnabled()) { + menu.findItem(R.id.menu_reset).setVisible(false); + } + + popupmenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { + @Override + public boolean onMenuItemClick(MenuItem item) { + return onPopupMenuItemClick(item); + } + }); + popupmenu.show(); + } + }); + mCustomize = (ImageView) v.findViewById(R.id.customize); + mCustomize.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + if (!isShowingConfirmCancelOverlay() && !isShowingCustomizeResetLayout()) { + getChooserActivity().expand(); + } + } + }); + + mDelete = (ImageView) v.findViewById(R.id.delete); + mDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showDeleteThemeOverlay(); + } + }); + if (Utils.getDefaultThemePackageName(getActivity()).equals(mPkgName) || + ThemeConfig.SYSTEM_DEFAULT.equals(mPkgName)) { + mDelete.setVisibility(View.GONE); + } + + mReset = (ImageView) v.findViewById(R.id.reset); + mReset.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showResetThemeOverlay(); + } + }); + mReset.setVisibility(View.GONE); + + if (!Utils.hasNavigationBar(getActivity())) { + adjustScrollViewPaddingTop(); + mNavBarCard.setVisibility(View.GONE); + } + + // Additional cards which should hang out offscreen until expanded + mAdditionalCards = (LinearLayout) v.findViewById(R.id.additional_cards); + + mWallpaperCard = (WallpaperCardView) v.findViewById(R.id.wallpaper_card); + mLockScreenCard = (WallpaperCardView) v.findViewById(R.id.lockscreen_card); + int translationY = getDistanceToMoveBelowScreen(mAdditionalCards); + mAdditionalCards.setTranslationY(translationY); + + mConfirmCancelOverlay = (ConfirmCancelOverlay) v.findViewById(R.id.confirm_cancel_overlay); + mClickableView = v.findViewById(R.id.clickable_view); + + mCustomizeResetLayout = v.findViewById(R.id.customize_reset_theme_layout); + mDismissButton = mCustomizeResetLayout.findViewById(R.id.btn_dismiss); + mDismissButton.setOnClickListener(mCustomizeResetClickListener); + mResetButton = mCustomizeResetLayout.findViewById(R.id.btn_reset); + mResetButton.setOnClickListener(mCustomizeResetClickListener); + mCustomizeButton = mCustomizeResetLayout.findViewById(R.id.btn_customize); + mCustomizeButton.setOnClickListener(mCustomizeResetClickListener); + + mProcessingThemeLayout = v.findViewById(R.id.processing_theme_layout); + + if (mPkgName.equals(Utils.getDefaultThemePackageName(getActivity()))) { + mThemeTagLayout.setDefaultTagEnabled(true); + } + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { + mThemeTagLayout.setUpdatedTagEnabled(true); + } + + if (mSkipLoadingAnim) { + mLoadingView.setVisibility(View.GONE); + mTitleLayout.setAlpha(1f); + } + + getLoaderManager().initLoader(LOADER_ID_ALL, null, this); + + setupCardClickListeners(v); + + return v; + } + + @Override + public void onPause() { + super.onPause(); + stopMediaPlayers(); + } + + @Override + public void onResume() { + super.onResume(); + ThemeManager tm = getThemeManager(); + if (tm != null) { + if (isThemeProcessing()) { + tm.registerProcessingListener(this); + mProcessingThemeLayout.setVisibility(View.VISIBLE); + mCustomize.setVisibility(View.INVISIBLE); + mCustomize.setAlpha(0f); + if (mDelete.getVisibility() != View.GONE) { + mDelete.setVisibility(View.INVISIBLE); + mDelete.setAlpha(0f); + } + mProcessingResources = true; + } else { + mCustomize.setVisibility(View.VISIBLE); + mCustomize.setAlpha(1f); + if (mDelete.getVisibility() != View.GONE) { + mDelete.setVisibility(View.VISIBLE); + mDelete.setAlpha(1f); + } + mProcessingResources = false; + } + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + freeMediaPlayers(); + ThemeManager tm = getThemeManager(); + if (tm != null) { + tm.removeClient(this); + tm.unregisterProcessingListener(this); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, + int[] grantResults) { + if (requestCode == PERMISSION_REQUEST) { + int N = permissions.length; + for (int i = 0; i < N; i++) { + if (READ_EXTERNAL_STORAGE.equals(permissions[i])) { + if (grantResults[i] == PERMISSION_GRANTED) { + // Run the runnable now that we have been granted permission + if (mAfterPermissionGrantedRunnable != null) { + mAfterPermissionGrantedRunnable.run(); + mAfterPermissionGrantedRunnable = null; + } + } else { + // inform the user that they will be unable to pick an image because + // we were not granted permission to do so + Toast.makeText(getActivity(), + R.string.read_external_permission_denied_message, + Toast.LENGTH_LONG).show(); + } + } + } + } + } + + @Override + public void onProgress(int progress) { + mProgress.setProgress(progress); + } + + private void setLiveLockScreenAsKeyguard(boolean setLLS) { + ComponentName cn = null; + if (setLLS) { + try { + final String[] permissions = Utils.getDangerousPermissionsNotGranted(getActivity(), + LLS_PACKAGE_NAME); + if (permissions.length > 0) { + Intent reqIntent = Utils.buildPermissionGrantRequestIntent(getActivity(), + LLS_PACKAGE_NAME, permissions); + if (reqIntent != null) { + startActivity(reqIntent); + } + } + cn = new ComponentName(LLS_PACKAGE_NAME, LLS_PROVIDER_NAME); + } catch (InvalidParameterException e) { + Log.e(TAG, "Package Manager couldn't find package " + LLS_PACKAGE_NAME, e); + return; + } + } + + CmLockPatternUtils lockPatternUtils = new CmLockPatternUtils(getActivity()); + try { + lockPatternUtils.setThirdPartyKeyguard(cn); + } catch (PackageManager.NameNotFoundException e) { + // we should not be here! + } + } + + @Override + public void onFinish(boolean isSuccess) { + // We post a runnable to mHandler so the client is removed from the same thread + mHandler.post(new Runnable() { + @Override + public void run() { + ThemeManager tm = getThemeManager(); + if (tm != null) tm.removeClient(ThemeFragment.this); + } + }); + if (isSuccess) { + if (mExternalLockscreenUri != null) { + // Handle setting an external wallpaper in a separate thread + // Need to do this AFTER ThemeMgr is done processing our change request. + // The external lock screen that we just applied would be removed when + // the change request is setting/clearing the lock screen + new Thread(mApplyExternalLockscreenRunnable).start(); + } + Map appliedComponents = getComponentsToApply(); + boolean modLLS = appliedComponents.containsKey(MODIFIES_LIVE_LOCK_SCREEN); + if (modLLS) { + String pkgName = appliedComponents.get(MODIFIES_LIVE_LOCK_SCREEN); + if (pkgName.equals(LOCKSCREEN_NONE)) { + setLiveLockScreenAsKeyguard(false); + } else { + setLiveLockScreenAsKeyguard(true); + } + } + mProgress.setProgress(100); + animateProgressOut(); + } + getChooserActivity().themeChangeEnd(isSuccess); + } + + @Override + public void onFinishedProcessing(String pkgName) { + if (pkgName.equals(mPkgName) || pkgName.equals(mBaseThemePkgName)) { + ThemeManager tm = getThemeManager(); + if (tm != null) { + tm.unregisterProcessingListener(this); + } + } + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (mThemeTagLayout == null) return; + + if (!isVisibleToUser) { + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { + mThemeTagLayout.setUpdatedTagEnabled(true); + } + } else { + if (PreferenceUtils.hasThemeBeenUpdated(getActivity(), mPkgName)) { + PreferenceUtils.removeUpdatedTheme(getActivity(), mPkgName); + } + } + } + + public void setWallpaperImageUri(Uri uri) { + mExternalWallpaperUri = uri; + final Point size = new Point(mWallpaper.getWidth(), mWallpaper.getHeight()); + final Drawable wp = getWallpaperDrawableFromUri(uri, size); + mWallpaperCard.setWallpaper(wp); + mWallpaper.setImageDrawable(wp); + // remove the entry from mSelectedComponentsMap + mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LAUNCHER); + } + + public void setLockscreenImageUri(Uri uri) { + mExternalLockscreenUri = uri; + final Point size = new Point(mLockScreenCard.getWidth(), mLockScreenCard.getHeight()); + final Drawable wp = getWallpaperDrawableFromUri(uri, size); + if (mLockScreenCard.isShowingEmptyView()) { + mLockScreenCard.setEmptyViewEnabled(false); + } + mLockScreenCard.setWallpaper(wp); + // remove the entry from mSelectedComponentsMap + mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN); + mSelectedComponentsMap.remove(ThemesColumns.MODIFIES_LOCKSCREEN); + } + + protected Drawable getWallpaperDrawableFromUri(Uri uri, Point size) { + final Context context = getActivity(); + final Resources res = context.getResources(); + Bitmap bmp = WallpaperUtils.createPreview(size, context, uri, null, res, 0, 0, false); + if (bmp != null) { + return new BitmapDrawable(res, bmp); + } + return null; + } + + protected ChooserActivity getChooserActivity() { + return (ChooserActivity) getActivity(); + } + + private void adjustScrollViewPaddingTop() { + Resources res = getResources(); + int extraPadding = + res.getDimensionPixelSize(R.dimen.navigation_bar_height) / 2; + mScrollView.setPadding(mScrollView.getPaddingLeft(), + mScrollView.getPaddingTop() + extraPadding, mScrollView.getPaddingRight(), + mScrollView.getPaddingBottom()); + } + + protected boolean isThemeProcessing() { + ThemeManager tm = getThemeManager(); + if (tm != null) { + final String pkgName = mBaseThemePkgName != null ? mBaseThemePkgName : mPkgName; + return tm.isThemeBeingProcessed(pkgName); + } + return false; + } + + protected boolean onPopupMenuItemClick(MenuItem item) { + switch(item.getItemId()) { + /* TODO: Add back in once there is UX available for this feature + case R.id.menu_author: + Toast.makeText(getActivity(), + "Not supported", + Toast.LENGTH_LONG).show(); + break; + */ + case R.id.menu_delete: + showDeleteThemeOverlay(); + break; + } + + return true; + } + + public void expand() { + if (mCurrentLoaderId == LOADER_ID_ALL && mCurrentCursor != null) { + loadAdditionalCards(mCurrentCursor); + // we don't need this now that the additional cards are loaded, and + // we don't want to re-load these cards if the we expand again. + mCurrentCursor = null; + } + mClickableView.setVisibility(View.GONE); + mScrollView.setScrollingEnabled(true); + // Full width and height! + ViewGroup content = (ViewGroup) mScrollView.getParent(); + content.setPadding(0, 0, 0, 0); + ViewGroup.LayoutParams layoutParams = mPreviewContent.getLayoutParams(); + layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT; + mPreviewContent.setLayoutParams(layoutParams); + mScrollView.setPadding(0,0,0,0); + + // The parent of the wallpaper squishes the wp slightly because of padding from the 9 patch + // When the parent expands, the wallpaper returns to regular size which creates an + // undesireable effect. + Rect padding = new Rect(); + NinePatchDrawable bg = (NinePatchDrawable) mShadowFrame.getBackground(); + bg.getPadding(padding); + mIconCard.setPadding(padding.left, padding.top, padding.right, padding.bottom); + mShadowFrame.setBackground(null); + mShadowFrame.setPadding(0, 0, 0, 0); + + // Off screen cards will become visible and then be animated in + mWallpaperCard.setVisibility(View.VISIBLE); + + // Expand the children + int top = (int) getResources() + .getDimension(R.dimen.expanded_card_margin_top); + for (int i = 0; i < mPreviewContent.getChildCount(); i++) { + ComponentCardView child = (ComponentCardView) mPreviewContent.getChildAt(i); + + LinearLayout.LayoutParams lparams = + (LinearLayout.LayoutParams) child.getLayoutParams(); + if (child == mStatusBarCard) { + int statusBarHeight = getResources() + .getDimensionPixelSize(R.dimen.status_bar_height); + lparams.setMargins(0, top + statusBarHeight, 0, 0); + } else { + lparams.setMargins(0, top, 0, 0); + } + + child.setLayoutParams(lparams); + child.expand(false); + } + + // Expand the additional children. + mAdditionalCards.setVisibility(View.VISIBLE); + for (int i = 0; i < mAdditionalCards.getChildCount(); i++) { + View v = mAdditionalCards.getChildAt(i); + if (v instanceof ComponentCardView) { + ComponentCardView card = (ComponentCardView) v; + card.setVisibility(View.VISIBLE); + card.expand(true); + } + } + + // Collect the present position of all the children. The next layout/draw cycle will + // change these bounds since we just expanded them. Then we can animate from prev location + // to the new location. Note that the order of these calls matter as they all + // add themselves to the root layout as overlays + mScrollView.requestLayout(); + animateWallpaperOut(); + animateTitleCard(true, false); + animateChildren(true, getChildrensGlobalBounds(mPreviewContent)); + animateExtras(true); + mSelector = getChooserActivity().getComponentSelector(); + mSelector.setOnItemClickedListener(mOnComponentItemClicked); + if (mBootAnimation != null) mBootAnimation.start(); + hideThemeTagLayout(); + mExpanded = true; + } + + + + // Returns the boundaries for all the children of parent relative to the app window + private List getChildrensGlobalBounds(ViewGroup parent) { + List bounds = new ArrayList(); + for (int i = 0; i < parent.getChildCount(); i++) { + final View v = parent.getChildAt(i); + int[] pos = new int[2]; + v.getLocationInWindow(pos); + Rect boundary = new Rect(pos[0], pos[1], pos[0] + v.getWidth(), pos[1]+v.getHeight()); + bounds.add(boundary); + } + return bounds; + } + + public void performClick(boolean clickedOnContent) { + // Don't do anything if the theme is being processed + if (mProcessingThemeLayout.getVisibility() == View.VISIBLE) return; + + if (clickedOnContent) { + showApplyThemeOverlay(); + } else { + if (isShowingConfirmCancelOverlay()) { + hideConfirmCancelOverlay(); + } + } + } + + public void fadeOutCards(Runnable endAction) { + for (int i = 0; i < mPreviewContent.getChildCount(); i++) { + ComponentCardView v = (ComponentCardView) mPreviewContent.getChildAt(i); + v.animateFadeOut(); + } + mHandler.postDelayed(endAction, ComponentCardView.CARD_FADE_DURATION); + } + + public void collapse(final boolean applyTheme) { + mScrollView.setScrollingEnabled(false); + + // Pad the view so it appears thinner + ViewGroup content = (ViewGroup) mScrollView.getParent(); + Resources r = mScrollView.getContext().getResources(); + int leftRightPadding = (int) r.getDimension(R.dimen.collapsed_theme_page_padding); + content.setPadding(leftRightPadding, 0, leftRightPadding, 0); + + if (applyTheme) { + final boolean customized = isThemeCustomized(); + mThemeTagLayout.setCustomizedTagEnabled(customized); + mReset.setVisibility(customized ? View.VISIBLE : View.GONE); + } + + //Move the theme preview so that it is near the center of page per spec + int paddingTop = (int) r.getDimension(R.dimen.collapsed_theme_page_padding_top); + if (!Utils.hasNavigationBar(getActivity())) { + paddingTop += + r.getDimensionPixelSize(R.dimen.navigation_bar_height) / 2; + } + mScrollView.setPadding(0, paddingTop, 0, 0); + + // During expand the wallpaper size decreases slightly to makeup for 9patch padding + // so when we collapse we should increase it again. + mShadowFrame.setBackgroundResource(R.drawable.bg_themepreview_shadow); + Rect padding = new Rect(); + final NinePatchDrawable bg = (NinePatchDrawable) mShadowFrame.getBackground(); + bg.getPadding(padding); + mShadowFrame.setPadding(padding.left, padding.top, padding.right, padding.bottom); + + // Gradually fade the drop shadow back in or else it will be out of place + ValueAnimator shadowAnimation = ValueAnimator.ofObject(new IntEvaluator(), 0, 255); + shadowAnimation.setDuration(ANIMATE_DURATION); + shadowAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animator) { + bg.setAlpha((Integer) animator.getAnimatedValue()); + } + + }); + shadowAnimation.start(); + + //Move the title card back in + mTitleCard.setVisibility(View.VISIBLE); + mTitleCard.setTranslationY(0); + + // Shrink the height + ViewGroup.LayoutParams layoutParams = mPreviewContent.getLayoutParams(); + Resources resources = mPreviewContent.getResources(); + layoutParams.height = (int) resources.getDimension(R.dimen.theme_preview_height); + + mScrollView.requestLayout(); + List bounds = getChildrensGlobalBounds(mPreviewContent); + for (int i = 0; i < mPreviewContent.getChildCount(); i++) { + ComponentCardView child = (ComponentCardView) mPreviewContent.getChildAt(i); + LinearLayout.LayoutParams lparams = + (LinearLayout.LayoutParams) child.getLayoutParams(); + lparams.setMargins(0, 0, 0, 0); + + if (child.getId() == R.id.icon_container) { + int top = (int) child.getResources() + .getDimension(R.dimen.collapsed_icon_card_margin_top); + lparams.setMargins(0, top, 0, 0); + } else if (child.getId() == R.id.font_preview_container) { + int top = (int) child.getResources() + .getDimension(R.dimen.collapsed_font_card_margin_top); + lparams.setMargins(0, top, 0, 0); + } else if (child.getId() == R.id.navigation_bar_container) { + int top = (int) child.getResources() + .getDimension(R.dimen.collapsed_navbar_card_margin_top); + lparams.setMargins(0, top, 0, 0); + } + + child.getLayoutParams(); + child.collapse(); + } + + // Collapse additional cards + for (int i = 0; i < mAdditionalCards.getChildCount(); i++) { + View v = mAdditionalCards.getChildAt(i); + if (v instanceof ComponentCardView) { + ComponentCardView card = (ComponentCardView) v; + card.setVisibility(View.VISIBLE); + card.collapse(); + } + } + + animateChildren(false, bounds); + animateExtras(false); + animateWallpaperIn(); + animateTitleCard(false, applyTheme); + if (mBootAnimation != null) mBootAnimation.stop(); + stopMediaPlayers(); + showThemeTagLayout(); + + // Need to set the wallpaper background to black if the user has selected to apply + // the "none" wallpaper + if (applyTheme) { + String pkgName = mSelectedComponentsMap.get(ThemesColumns.MODIFIES_LAUNCHER); + if (pkgName != null && pkgName.length() == 0) { + mWallpaper.setImageResource(R.drawable.wallpaper_none_bg); + } + // we do this here instead of in applyTheme() because this can take a bit longer + // to propagate the change from WallpaperManager back to us + if (mExternalWallpaperUri != null) { + // Handle setting an external wallpaper in a separate thread + new Thread(mApplyExternalWallpaperRunnable).start(); + } + } + mExpanded = false; + } + + // This will animate the children's vertical positions between the previous bounds and the + // new bounds which occur on the next draw + private void animateChildren(final boolean isExpanding, final List prevBounds) { + final ViewGroup root = (ViewGroup) getActivity().getWindow() + .getDecorView().findViewById(android.R.id.content); + + final Resources res = getResources(); + final float yOffset = + res.getDimensionPixelSize(R.dimen.expand_collapse_child_offset) + * (isExpanding ? -1 : 1); + // Grab the child's new location and animate from prev to current loc. + final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + + for (int i = mPreviewContent.getChildCount() - 1; i >= 0; i--) { + final ComponentCardView v = (ComponentCardView) mPreviewContent.getChildAt(i); + + float prevY; + float endY; + float prevHeight; + float endHeight; + if (i >= prevBounds.size()) { + // View is being created + prevY = mPreviewContent.getTop() + mPreviewContent.getHeight(); + endY = v.getY(); + prevHeight = v.getHeight(); + endHeight = v.getHeight(); + } else { + Rect boundary = prevBounds.get(i); + prevY = boundary.top; + prevHeight = boundary.height(); + + int[] endPos = new int[2]; + v.getLocationInWindow(endPos); + endY = endPos[1]; + endHeight = v.getHeight(); + } + + int paddingTop = v.getPaddingTop() / 2; + float dy = (prevY - endY - paddingTop) + (prevHeight - endHeight) / 2; + dy += yOffset; + v.setTranslationY(dy); + root.getOverlay().add(v); + + // Expanding has a delay while the wallpaper begins to fade out + // Collapsing is opposite of this so wallpaper will have the delay instead + int startDelay = isExpanding ? ANIMATE_START_DELAY : 0; + + v.animate() + .setStartDelay(startDelay) + .translationY(0) + .setDuration(ANIMATE_DURATION) + .setInterpolator( + new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)) + .withEndAction(new Runnable() { + public void run() { + root.getOverlay().remove(v); + mPreviewContent.addView(v, 0); + } + }); + v.postDelayed(new Runnable() { + public void run() { + if (isExpanding) { + v.animateExpand(); + } + } + }, ANIMATE_DURATION / 2); + } + return true; + } + }); + } + + private void animateExtras(final boolean isExpanding) { + int[] pos = new int[2]; + mAdditionalCards.getLocationInWindow(pos); + final ViewGroup parent = (ViewGroup) mAdditionalCards.getParent(); + final ViewGroup root = (ViewGroup) getActivity().getWindow() + .getDecorView().findViewById(android.R.id.content); + + // During a collapse we don't want the card to shrink so add it to the overlay now + // During an expand we want the card to expand so add it to the overlay post-layout + if (!isExpanding) { + root.getOverlay().add(mAdditionalCards); + } + + // Expanding has a delay while the wallpaper begins to fade out + // Collapsing is opposite of this so wallpaper will have the delay instead + final int startDelay = isExpanding ? ANIMATE_START_DELAY : 0; + final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + + int translationY = 0; + if (isExpanding) { + root.getOverlay().add(mAdditionalCards); + } else { + translationY = getDistanceToMoveBelowScreen(mAdditionalCards); + } + + int duration = isExpanding ? ANIMATE_DURATION + 100 : ANIMATE_DURATION; + mAdditionalCards.animate() + .setStartDelay(startDelay) + .translationY(translationY) + .setDuration(duration) + .setInterpolator( + new DecelerateInterpolator(ANIMATE_INTERPOLATE_FACTOR)) + .withEndAction(new Runnable() { + public void run() { + if (!isExpanding) { + mAdditionalCards.setVisibility(View.INVISIBLE); + } + root.getOverlay().remove(mAdditionalCards); + parent.addView(mAdditionalCards); + } + }); + return false; + } + }); + } + + private int getDistanceToMoveBelowScreen(View v) { + Display display = getActivity().getWindowManager().getDefaultDisplay(); + Point p = new Point(); + display.getSize(p); + int heightId = getResources() + .getIdentifier("navigation_bar_height", "dimen", "android"); + int navbar_height = getResources().getDimensionPixelSize(heightId); + int[] pos = new int[2]; + v.getLocationInWindow(pos); + return p.y + navbar_height - pos[1]; + } + + private void animateTitleCard(final boolean expand, final boolean applyTheme) { + final ViewGroup parent = (ViewGroup) mTitleCard.getParent(); + // Get current location of the title card + int[] location = new int[2]; + mTitleCard.getLocationOnScreen(location); + final int prevY = location[1]; + final int position = parent.indexOfChild(mTitleCard); + + final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + + final ViewGroup root = (ViewGroup) getActivity().getWindow() + .getDecorView().findViewById(android.R.id.content); + + root.getOverlay().add(mTitleCard); + + //Move title card back where it was before the relayout + float alpha = 1f; + if (expand) { + int[] endPos = new int[2]; + mTitleCard.getLocationInWindow(endPos); + int endY = endPos[1]; + mTitleCard.setTranslationY(prevY - endY); + alpha = 0; + } else { + } + + // Fade the title card and move it out of the way + mTitleCard.animate() + .alpha(alpha) + .setDuration(ANIMATE_DURATION) + .withEndAction(new Runnable() { + public void run() { + root.getOverlay().remove(mTitleCard); + parent.addView(mTitleCard, position); + if (expand) { + mTitleCard.setVisibility(View.INVISIBLE); + } else { + mTitleCard.setVisibility(View.VISIBLE); + mClickableView.setVisibility(View.VISIBLE); + if (applyTheme) { + // The title card is the last animation when collapsing so + // we will handle applying the theme, if applicable, here + applyTheme(); + } + } + } + }); + return true; + } + }); + } + + private void animateWallpaperOut() { + final ViewGroup root = (ViewGroup) getActivity().getWindow() + .getDecorView().findViewById(android.R.id.content); + + int[] location = new int[2]; + mWallpaper.getLocationOnScreen(location); + + final int prevY = location[1]; + + final ViewTreeObserver observer = mScrollContent.getViewTreeObserver(); + observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { + public boolean onPreDraw() { + observer.removeOnPreDrawListener(this); + root.getOverlay().add(mWallpaper); + + int[] location = new int[2]; + mWallpaper.getLocationOnScreen(location); + final int newY = location[1]; + + mWallpaper.setTranslationY(prevY - newY); + mWallpaper.animate() + .alpha(0f) + .setDuration(300) + .withEndAction(new Runnable() { + public void run() { + root.getOverlay().remove(mWallpaper); + mShadowFrame.addView(mWallpaper, 0); + mWallpaper.setVisibility(View.GONE); + } + }); + return true; + + } + }); + } + + private void animateWallpaperIn() { + mWallpaper.setVisibility(View.VISIBLE); + mWallpaper.setTranslationY(0); + mWallpaper.animate() + .alpha(1f) + .setDuration(300); + } + + protected String getAppliedFontPackageName() { + final Configuration config = getActivity().getResources().getConfiguration(); + final ThemeConfig themeConfig = config != null ? config.themeConfig : null; + return themeConfig != null ? themeConfig.getFontPkgName() : + ThemeConfig.getSystemTheme().getFontPkgName(); + } + + protected ThemeManager getThemeManager() { + return ThemeManager.getInstance(getActivity()); + } + + private void freeMediaPlayers() { + for (MediaPlayer mp : mMediaPlayers.values()) { + if (mp != null) { + mp.stop(); + mp.release(); + } + } + mMediaPlayers.clear(); + } + + protected View.OnClickListener mPlayPauseClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + MediaPlayer mp = (MediaPlayer) v.getTag(); + if (mp != null) { + if (mp.isPlaying()) { + ((ImageView) v).setImageResource(R.drawable.media_sound_preview); + mp.pause(); + mp.seekTo(0); + } else { + stopMediaPlayers(); + ((ImageView) v).setImageResource(R.drawable.media_sound_stop); + mp.start(); + } + } + } + }; + + protected MediaPlayer.OnCompletionListener mPlayCompletionListener + = new MediaPlayer.OnCompletionListener() { + @Override + public void onCompletion(MediaPlayer mp) { + for (ImageView v : mMediaPlayers.keySet()) { + if (mp == mMediaPlayers.get(v)) { + if (v != null) { + v.setImageResource(R.drawable.media_sound_preview); + } + } + } + } + }; + + private void stopMediaPlayers() { + for (ImageView v : mMediaPlayers.keySet()) { + if (v != null) { + v.setImageResource(R.drawable.media_sound_preview); + } + MediaPlayer mp = mMediaPlayers.get(v); + if (mp != null && mp.isPlaying()) { + mp.pause(); + mp.seekTo(0); + } + } + } + + protected void resetTheme() { + mSelectedComponentsMap.clear(); + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, mBaseThemePkgName); + args.putLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); + getLoaderManager().restartLoader(LOADER_ID_ALL, args, this); + mThemeResetting = true; + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + String pkgName = mPkgName; + long componentId = DEFAULT_COMPONENT_ID; + if (args != null) { + pkgName = args.getString(ARG_PACKAGE_NAME); + componentId = args.getLong(ARG_COMPONENT_ID, DEFAULT_COMPONENT_ID); + } + return CursorLoaderHelper.themeFragmentCursorLoader(getActivity(), id, pkgName, + componentId); + } + + @Override + public void onLoadFinished(Loader loader, Cursor c) { + if (c.getCount() == 0) return; + mCurrentCursor = c; + mCurrentLoaderId = loader.getId(); + c.moveToFirst(); + boolean animate = !mApplyThemeOnPopulated; + switch (mCurrentLoaderId) { + case LOADER_ID_ALL: + if (mProcessingResources && !isThemeProcessing()) { + mProcessingResources = false; + hideProcessingOverlay(); + } + loadLegacyThemeInfo(c); + populateSupportedComponents(c); + loadWallpaper(c, false); + loadStatusBar(c, false); + loadIcons(c, false); + loadNavBar(c, false); + loadTitle(c); + loadFont(c, false); + mHandler.post(new Runnable() { + @Override + public void run() { + animateContentIn(); + } + }); + if (mShowLockScreenSelectorAfterContentLoaded) { + getChooserActivity().expandContentAndAnimateLockScreenCardIn(); + mShowLockScreenSelectorAfterContentLoaded = false; + } + break; + case LOADER_ID_STATUS_BAR: + loadStatusBar(c, animate); + break; + case LOADER_ID_FONT: + loadFont(c, animate); + break; + case LOADER_ID_ICONS: + loadIcons(c, animate); + break; + case LOADER_ID_WALLPAPER: + loadWallpaper(c, animate); + break; + case LOADER_ID_NAVIGATION_BAR: + loadNavBar(c, animate); + break; + case LOADER_ID_LIVE_LOCK_SCREEN: + case LOADER_ID_LOCKSCREEN: + loadLockScreen(c, animate); + break; + case LOADER_ID_STYLE: + loadStyle(c, animate); + break; + case LOADER_ID_BOOT_ANIMATION: + loadBootAnimation(c); + break; + case LOADER_ID_RINGTONE: + loadAudible(RingtoneManager.TYPE_RINGTONE, c, animate); + break; + case LOADER_ID_NOTIFICATION: + loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, animate); + break; + case LOADER_ID_ALARM: + loadAudible(RingtoneManager.TYPE_ALARM, c, animate); + break; + } + if (mCurrentLoaderId != LOADER_ID_ALL) { + if (!componentsChanged()) { + getChooserActivity().hideSaveApplyButton(); + } else if (!mApplyThemeOnPopulated) { + getChooserActivity().showSaveApplyButton(); + } + } + } + + @Override + public void onLoaderReset(Loader loader) {} + + private void loadAdditionalCards(Cursor c) { + for(int i=0; i < mAdditionalCards.getChildCount(); i++) { + View v = mAdditionalCards.getChildAt(i); + if (v instanceof ComponentCardView) { + String component = mCardIdsToComponentTypes.get(v.getId()); + loadAdditionalCard(c, component, shouldShowComponentCard(component)); + } + } + } + + private void loadAdditionalCard(Cursor c, String component, boolean hasContent) { + if (MODIFIES_LOCKSCREEN.equals(component)) { + if (hasContent) { + loadLockScreen(c, false); + } else { + mLockScreenCard.clearWallpaper(); + mLockScreenCard.setEmptyViewEnabled(true); + setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); + } + } else if (MODIFIES_LAUNCHER.equals(component)) { + // this was already loaded so no need to do this again. + } else if (MODIFIES_OVERLAYS.equals(component)) { + if (hasContent) { + loadStyle(c, false); + } else { + mStyleCard.setEmptyViewEnabled(true); + setAddComponentTitle(mStyleCard, + getString(R.string.style_label)); + } + } else if (MODIFIES_BOOT_ANIM.equals(component)) { + if (hasContent) { + loadBootAnimation(c); + } else { + mBootAnimationCard.setEmptyViewEnabled(true); + setAddComponentTitle(mBootAnimationCard, + getString(R.string.boot_animation_label)); + } + } else if (MODIFIES_RINGTONES.equals(component)) { + if (hasContent) { + loadAudible(RingtoneManager.TYPE_RINGTONE, c, false); + } else { + mRingtoneCard.setEmptyViewEnabled(true); + setAddComponentTitle(mRingtoneCard, + getAudibleLabel(RingtoneManager.TYPE_RINGTONE)); + } + } else if (MODIFIES_NOTIFICATIONS.equals(component)) { + if (hasContent) { + loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, false); + } else { + mNotificationCard.setEmptyViewEnabled(true); + setAddComponentTitle(mNotificationCard, + getAudibleLabel(RingtoneManager.TYPE_NOTIFICATION)); + } + } else if (MODIFIES_ALARMS.equals(component)) { + if (hasContent) { + loadAudible(RingtoneManager.TYPE_ALARM, c, false); + } else { + mAlarmCard.setEmptyViewEnabled(true); + setAddComponentTitle(mAlarmCard, + getAudibleLabel(RingtoneManager.TYPE_ALARM)); + } + } else { + throw new IllegalArgumentException("Don't know how to load: " + component); + } + } + + protected void populateSupportedComponents(Cursor c) { + List components = ThemeUtils.getAllComponents(); + for(String component : components) { + int pkgIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int modifiesCompIdx = c.getColumnIndex(component); + + String pkg = c.getString(pkgIdx); + boolean supported = (modifiesCompIdx >= 0) && (c.getInt(modifiesCompIdx) == 1); + if (supported) { + mBaseThemeSupportedComponents.add(component); + mSelectedComponentsMap.put(component, pkg); + } + } + + if (mApplyThemeOnPopulated) { + applyTheme(); + } + } + + /** + * Determines whether a card should be shown or not. + * UX Rules: + * 1) "My Theme" always shows all cards + * 2) Other themes only show what has been implemented in the theme + * + */ + protected Boolean shouldShowComponentCard(String component) { + String pkg = mSelectedComponentsMap.get(component); + return pkg != null && pkg.equals(mPkgName); + } + + protected void loadLegacyThemeInfo(Cursor c) { + int targetApiIdx = c.getColumnIndex(ThemesColumns.TARGET_API); + // If this is being called for a MyThemeFragment the index will be -1 so set to + // SYSTEM_TARGET_API so we don't display the tag. If the user applied a legacy theme + // then they should have already been warned. + int targetApi = targetApiIdx < 0 ? SYSTEM_TARGET_API : c.getInt(targetApiIdx); + mIsLegacyTheme = targetApi != SYSTEM_TARGET_API && targetApi <= Build.VERSION_CODES.KITKAT; + mThemeTagLayout.setLegacyTagEnabled(mIsLegacyTheme); + } + + protected void loadTitle(Cursor c) { + int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); + int authorIdx = c.getColumnIndex(ThemesColumns.AUTHOR); + mTitle.setText(c.getString(titleIdx)); + mAuthor.setText(c.getString(authorIdx)); + } + + protected void loadWallpaper(Cursor c, boolean animate) { + mExternalWallpaperUri = null; + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mWallpaperCard, true); + } + if (mWallpaperCard.isShowingEmptyView()) mWallpaperCard.setEmptyViewEnabled(false); + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int wpIdx = c.getColumnIndex(PreviewColumns.WALLPAPER_PREVIEW); + int cmpntIdIdx = c.getColumnIndex(PreviewColumns.COMPONENT_ID); + final Resources res = getResources(); + Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx); + if (bitmap != null) { + mWallpaper.setImageBitmap(bitmap); + mWallpaperCard.setWallpaper(new BitmapDrawable(res, bitmap)); + String pkgName = c.getString(pkgNameIdx); + Long cmpntId = (cmpntIdIdx >= 0) ? c.getLong(cmpntIdIdx) : DEFAULT_COMPONENT_ID; + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_LAUNCHER))) { + mSelectedComponentsMap.put(MODIFIES_LAUNCHER, pkgName); + mSelectedWallpaperComponentId = cmpntId; + setCardTitle(mWallpaperCard, pkgName, getString(R.string.wallpaper_label)); + } + } else { + // Set the wallpaper to "None" + mWallpaperCard.setWallpaper(null); + setCardTitle(mWallpaperCard, WALLPAPER_NONE, getString(R.string.wallpaper_label)); + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, WALLPAPER_NONE); + } + + if (animate) { + animateContentChange(R.id.wallpaper_card, mWallpaperCard, overlay); + } + } + + protected void loadLockScreen(Cursor c, boolean animate) { + mExternalLockscreenUri = null; + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mLockScreenCard, true); + } + if (mLockScreenCard.isShowingEmptyView()) mLockScreenCard.setEmptyViewEnabled(false); + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int liveLockIndex = c.getColumnIndex(MODIFIES_LIVE_LOCK_SCREEN); + boolean isLiveLockScreen = liveLockIndex >= 0 && c.getInt(liveLockIndex) == 1; + + int wpIdx = isLiveLockScreen + ? c.getColumnIndex(PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW) + : c.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_PREVIEW); + final Resources res = getResources(); + Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx); + if (bitmap != null) { + mLockScreenCard.setWallpaper(new BitmapDrawable(res, bitmap)); + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && (mBaseThemeSupportedComponents.contains(MODIFIES_LOCKSCREEN) || + mBaseThemeSupportedComponents.contains(MODIFIES_LIVE_LOCK_SCREEN)))) { + if (isLiveLockScreen) { + mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, pkgName); + if (mCurrentTheme.containsKey(MODIFIES_LOCKSCREEN)) { + mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); + } else { + mSelectedComponentsMap.remove(MODIFIES_LOCKSCREEN); + } + setCardTitle(mLockScreenCard, pkgName, + getString(R.string.live_lock_screen_label)); + } else { + mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, pkgName); + if (mCurrentTheme.containsKey(MODIFIES_LIVE_LOCK_SCREEN)) { + mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); + } else { + mSelectedComponentsMap.remove(MODIFIES_LIVE_LOCK_SCREEN); + } + setCardTitle(mLockScreenCard, pkgName, getString(R.string.lockscreen_label)); + } + } + } else { + // Set the lockscreen wallpaper to "None" + mLockScreenCard.setWallpaper(null); + setCardTitle(mLockScreenCard, WALLPAPER_NONE, getString(R.string.lockscreen_label)); + } + + if (animate) { + animateContentChange(R.id.lockscreen_card, mLockScreenCard, overlay); + } + } + + protected void loadStatusBar(Cursor c, boolean animate) { + int backgroundIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); + int wifiIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_ICON); + int wifiMarginIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END); + int bluetoothIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BLUETOOTH_ICON); + int signalIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_SIGNAL_ICON); + int batteryIdx = c.getColumnIndex(Utils.getBatteryIndex(mBatteryStyle)); + int clockColorIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR); + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + + Bitmap background = Utils.loadBitmapBlob(c, backgroundIdx); + Bitmap bluetoothIcon = Utils.loadBitmapBlob(c, bluetoothIdx); + Bitmap wifiIcon = Utils.loadBitmapBlob(c, wifiIdx); + Bitmap signalIcon = Utils.loadBitmapBlob(c, signalIdx); + Bitmap batteryIcon = Utils.loadBitmapBlob(c, batteryIdx); + int wifiMargin = wifiMarginIdx != -1 ? c.getInt(wifiMarginIdx) : DEFAULT_WIFI_MARGIN; + int clockTextColor = clockColorIdx != -1 ? c.getInt(clockColorIdx) : DEFAULT_CLOCK_COLOR; + + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mStatusBar, false); + } + if (mStatusBarCard.isShowingEmptyView()) mStatusBarCard.setEmptyViewEnabled(false); + + mStatusBar.setBackground(new BitmapDrawable(getActivity().getResources(), background)); + mBluetooth.setImageBitmap(bluetoothIcon); + mWifi.setImageBitmap(wifiIcon); + mSignal.setImageBitmap(signalIcon); + mBattery.setImageBitmap(batteryIcon); + mClock.setTextColor(clockTextColor); + + ViewGroup.MarginLayoutParams params = + (ViewGroup.MarginLayoutParams) mWifi.getLayoutParams(); + params.setMarginEnd(wifiMargin); + mWifi.setLayoutParams(params); + + if (mBatteryStyle == 4) { + mBattery.setVisibility(View.GONE); + } else { + mBattery.setVisibility(View.VISIBLE); + } + mStatusBar.post(new Runnable() { + @Override + public void run() { + mStatusBar.invalidate(); + } + }); + if (pkgNameIdx > -1) { + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_STATUS_BAR))) { + mSelectedComponentsMap.put(MODIFIES_STATUS_BAR, pkgName); + setCardTitle(mStatusBarCard, pkgName, + getString(R.string.statusbar_label)); + } + } + if (animate) { + animateContentChange(R.id.status_bar_container, mStatusBar, overlay); + } + } + + protected void loadIcons(Cursor c, boolean animate) { + if (mIconCard.isShowingEmptyView()) { + mIconCard.setEmptyViewEnabled(false); + } + int[] iconIdx = new int[3]; + iconIdx[0] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_1); + iconIdx[1] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_2); + iconIdx[2] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_3); + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + + // Set the icons. If the provider does not have an icon preview then + // fall back to the default icon set + IconPreviewHelper helper = new IconPreviewHelper(getActivity(), ""); + ViewGroup iconContainer = + (ViewGroup) mIconCard.findViewById(R.id.icon_preview_container); + int numOfChildren = iconContainer.getChildCount(); + + List iconViews = new ArrayList(numOfChildren); + for(int i=0; i < numOfChildren; i++) { + final View view = iconContainer.getChildAt(i); + if (!(view instanceof ImageView)) continue; + iconViews.add((ImageView) view); + } + + for(int i=0; i < iconViews.size() && i < iconIdx.length; i++) { + final ImageView v = iconViews.get(i); + Bitmap bitmap = Utils.loadBitmapBlob(c, iconIdx[i]); + Drawable oldIcon = v.getDrawable(); + Drawable newIcon; + if (bitmap == null) { + ComponentName component = sIconComponents[i]; + newIcon = helper.getDefaultIcon(component.getPackageName(), + component.getClassName()); + } else { + newIcon = new BitmapDrawable(getResources(), bitmap); + } + if (animate) { + Drawable[] layers = new Drawable[2]; + layers[0] = oldIcon instanceof IconTransitionDrawable ? + ((IconTransitionDrawable) oldIcon).getDrawable(1) : oldIcon; + layers[1] = newIcon; + final IconTransitionDrawable itd = new IconTransitionDrawable(layers); + v.postDelayed(new Runnable() { + @Override + public void run() { + itd.startTransition(ANIMATE_COMPONENT_CHANGE_DURATION); + v.setImageDrawable(itd); + } + }, ANIMATE_COMPONENT_ICON_DELAY * i); + } else { + v.setImageDrawable(newIcon); + } + } + if (pkgNameIdx > -1) { + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_ICONS))) { + mSelectedComponentsMap.put(MODIFIES_ICONS, pkgName); + setCardTitle(mIconCard, pkgName, + getString(R.string.icon_label)); + } + } + } + + protected void loadNavBar(Cursor c, boolean animate) { + int backButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_BACK_BUTTON); + int homeButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_HOME_BUTTON); + int recentButtonIdx = c.getColumnIndex(PreviewColumns.NAVBAR_RECENT_BUTTON); + int backgroundIdx = c.getColumnIndex(PreviewColumns.NAVBAR_BACKGROUND); + if (backgroundIdx == -1) { + backgroundIdx = c.getColumnIndex(PreviewColumns.STATUSBAR_BACKGROUND); + } + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + + Bitmap background = Utils.loadBitmapBlob(c, backgroundIdx); + Bitmap backButton = Utils.loadBitmapBlob(c, backButtonIdx); + Bitmap homeButton = Utils.loadBitmapBlob(c, homeButtonIdx); + Bitmap recentButton = Utils.loadBitmapBlob(c, recentButtonIdx); + + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mNavBar, false); + } + if (mNavBarCard.isShowingEmptyView()) mNavBarCard.setEmptyViewEnabled(false); + + mNavBar.setBackground(new BitmapDrawable(getActivity().getResources(), background)); + mBackButton.setImageBitmap(backButton); + mHomeButton.setImageBitmap(homeButton); + mRecentButton.setImageBitmap(recentButton); + + if (pkgNameIdx > -1) { + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_NAVIGATION_BAR))) { + mSelectedComponentsMap.put(MODIFIES_NAVIGATION_BAR, pkgName); + setCardTitle(mNavBarCard, pkgName, getString(R.string.navbar_label)); + } + } + if (animate) { + animateContentChange(R.id.navigation_bar_container, mNavBar, overlay); + } + } + + protected void loadFont(Cursor c, boolean animate) { + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mFontPreview, true); + } + if (mFontCard.isShowingEmptyView()) mFontCard.setEmptyViewEnabled(false); + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + String pkgName = pkgNameIdx >= 0 ? c.getString(pkgNameIdx) : mPkgName; + TypefaceHelperCache cache = TypefaceHelperCache.getInstance(); + ThemedTypefaceHelper helper = cache.getHelperForTheme(getActivity(), pkgName); + mTypefaceNormal = helper.getTypeface(Typeface.NORMAL); + mFontPreview.setTypeface(mTypefaceNormal); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_FONTS))) { + mSelectedComponentsMap.put(MODIFIES_FONTS, pkgName); + setCardTitle(mFontCard, pkgName, getString(R.string.font_label)); + } + + if (animate) { + animateContentChange(R.id.font_preview_container, mFontPreview, overlay); + } + } + + protected void loadStyle(Cursor c, boolean animate) { + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(mStylePreview, true); + } + if (mStyleCard.isShowingEmptyView()) { + mStyleCard.setEmptyViewEnabled(false); + } + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int styleIdx = c.getColumnIndex(PreviewColumns.STYLE_PREVIEW); + mStylePreview.setImageBitmap(Utils.loadBitmapBlob(c, styleIdx)); + if (pkgNameIdx > -1) { + String pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_OVERLAYS))) { + mSelectedComponentsMap.put(MODIFIES_OVERLAYS, pkgName); + setCardTitle(mStyleCard, pkgName, + getString(R.string.style_label)); + } + } + if (animate) { + animateContentChange(R.id.style_card, mStylePreview, overlay); + } + } + + protected void loadBootAnimation(Cursor c) { + if (mBootAnimationCard.isShowingEmptyView()) { + mBootAnimationCard.setEmptyViewEnabled(false); + } + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + if (mBootAnimation != null) { + String pkgName; + if (pkgNameIdx > -1) { + pkgName = c.getString(pkgNameIdx); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(MODIFIES_BOOT_ANIM))) { + mSelectedComponentsMap.put(MODIFIES_BOOT_ANIM, pkgName); + setCardTitle(mBootAnimationCard, pkgName, + getString(R.string.boot_animation_label)); + } + } else { + pkgName = mCurrentTheme.get(MODIFIES_BOOT_ANIM); + } + mBootAnimation.stop(); + new AnimationLoader(getActivity(), pkgName, mBootAnimation).execute(); + } + } + + protected void loadAudible(int type, Cursor c, boolean animate) { + ComponentCardView audibleContainer = null; + ImageView playPause = null; + String component = null; + int parentResId = 0; + switch (type) { + case RingtoneManager.TYPE_RINGTONE: + audibleContainer = mRingtoneCard; + playPause = mRingtonePlayPause; + component = MODIFIES_RINGTONES; + parentResId = R.id.ringtone_preview_container; + break; + case RingtoneManager.TYPE_NOTIFICATION: + audibleContainer = mNotificationCard; + playPause = mNotificationPlayPause; + component = MODIFIES_NOTIFICATIONS; + parentResId = R.id.notification_preview_container; + break; + case RingtoneManager.TYPE_ALARM: + audibleContainer = mAlarmCard; + playPause = mAlarmPlayPause; + component = MODIFIES_ALARMS; + parentResId = R.id.alarm_preview_container; + break; + } + if (audibleContainer == null) return; + + View content = audibleContainer.findViewById(R.id.content); + Drawable overlay = null; + if (animate) { + overlay = getOverlayDrawable(content, true); + } + if (audibleContainer.isShowingEmptyView()) { + audibleContainer.setEmptyViewEnabled(false); + } + + int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME); + int titleIdx = c.getColumnIndex(ThemesColumns.TITLE); + if (playPause == null) { + playPause = (ImageView) audibleContainer.findViewById(R.id.play_pause); + } + TextView title = (TextView) audibleContainer.findViewById(R.id.audible_name); + MediaPlayer mp = mMediaPlayers.get(playPause); + if (mp == null) { + mp = new MediaPlayer(); + } + String pkgName = c.getString(pkgNameIdx); + setCardTitle(audibleContainer, pkgName, getAudibleLabel(type)); + AudibleLoadingThread thread = new AudibleLoadingThread(getActivity(), type, pkgName, mp); + title.setText(c.getString(titleIdx)); + if (!mPkgName.equals(pkgName) || (mPkgName.equals(pkgName) + && mBaseThemeSupportedComponents.contains(component))) { + mSelectedComponentsMap.put(component, pkgName); + } + + playPause.setVisibility(View.VISIBLE); + playPause.setTag(mp); + mMediaPlayers.put(playPause, mp); + playPause.setOnClickListener(mPlayPauseClickListener); + mp.setOnCompletionListener(mPlayCompletionListener); + if (animate) { + animateContentChange(parentResId, content, overlay); + } + thread.start(); + } + + protected Drawable getOverlayDrawable(View v, boolean requiresTransparency) { + if (!v.isDrawingCacheEnabled()) v.setDrawingCacheEnabled(true); + Bitmap cache = v.getDrawingCache(true).copy( + requiresTransparency ? Config.ARGB_8888 : Config.RGB_565, false); + Drawable d = cache != null ? new BitmapDrawable(getResources(), cache) : null; + v.destroyDrawingCache(); + + return d; + } + + protected String getAudibleLabel(int type) { + switch (type) { + case RingtoneManager.TYPE_RINGTONE: + return getString(R.string.ringtone_label); + case RingtoneManager.TYPE_NOTIFICATION: + return getString(R.string.notification_label); + case RingtoneManager.TYPE_ALARM: + return getString(R.string.alarm_label); + } + return null; + } + + protected void setCardTitle(ComponentCardView card, String pkgName, String title) { + TextView tv = (TextView) card.findViewById(R.id.label); + if (Utils.getDefaultThemePackageName(getActivity()).equals(pkgName)) { + tv.setText(getString(R.string.default_tag_text) + " " + title); + } else { + tv.setText(title); + } + } + + protected void setAddComponentTitle(ComponentCardView card, String title) { + TextView tv = (TextView) card.findViewById(R.id.label); + tv.setText(getString(R.string.add_component_text) + " " + title); + } + + public static ComponentName[] getIconComponents(Context context) { + if (sIconComponents == null || sIconComponents.length == 0) { + sIconComponents = new ComponentName[]{COMPONENT_DIALER, COMPONENT_MESSAGING, + COMPONENT_CAMERA, COMPONENT_BROWSER}; + + PackageManager pm = context.getPackageManager(); + + // if device does not have telephony replace dialer and mms + if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) { + sIconComponents[0] = COMPONENT_CALENDAR; + sIconComponents[1] = COMPONENT_GALERY; + } else { + // decide on which dialer icon to use + try { + if (pm.getPackageInfo(DIALER_NEXT_PACKAGE, 0) != null) { + sIconComponents[0] = COMPONENT_DIALERNEXT; + } + } catch (PackageManager.NameNotFoundException e) { + // default to COMPONENT_DIALER + } + } + + if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) { + sIconComponents[2] = COMPONENT_SETTINGS; + } else { + // decide on which camera icon to use + try { + if (pm.getPackageInfo(CAMERA_NEXT_PACKAGE, 0) != null) { + sIconComponents[2] = COMPONENT_CAMERANEXT; + } + } catch (PackageManager.NameNotFoundException e) { + // default to COMPONENT_CAMERA + } + } + + } + return sIconComponents; + } + + private void setupCardClickListeners(View parent) { + for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { + parent.findViewById(mCardIdsToComponentTypes.keyAt(i)) + .setOnClickListener(mCardClickListener); + } + } + + private View.OnClickListener mCardClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (isShowingConfirmCancelOverlay() || isShowingCustomizeResetLayout()) return; + if (mActiveCardId > 0) { + // need to fade the newly selected card in if another was currently selected. + ((ComponentCardView) v).animateCardFadeIn(); + } + mActiveCardId = v.getId(); + String component = mCardIdsToComponentTypes.get(mActiveCardId); + // Only pass on mSelectedWallpaperComponentId if dealing with mods_launcher + long selectedComponentId = (ThemesColumns.MODIFIES_LAUNCHER.equals(component)) ? + mSelectedWallpaperComponentId : DEFAULT_COMPONENT_ID; + String pkgName = mSelectedComponentsMap.get(component); + if (component.equals(MODIFIES_LOCKSCREEN) && TextUtils.isEmpty(pkgName)) { + String liveLockScreenPkg = mSelectedComponentsMap.get(MODIFIES_LIVE_LOCK_SCREEN); + if (liveLockScreenPkg != null) { + pkgName = liveLockScreenPkg; + } + } + getChooserActivity().showComponentSelector(component, pkgName, selectedComponentId, v); + fadeOutNonSelectedCards(mActiveCardId); + stopMediaPlayers(); + } + }; + + private ConfirmCancelOverlay.OnOverlayDismissedListener mApplyCancelListener = + new ConfirmCancelOverlay.OnOverlayDismissedListener() { + @Override + public void onDismissed(boolean accepted) { + hideConfirmCancelOverlay(accepted); + } + }; + + private ConfirmCancelOverlay.OnOverlayDismissedListener mDeleteConfirmationListener = + new ConfirmCancelOverlay.OnOverlayDismissedListener() { + @Override + public void onDismissed(boolean accepted) { + if (accepted) uninstallTheme(); + hideConfirmCancelOverlay(); + } + }; + + private ConfirmCancelOverlay.OnOverlayDismissedListener mResetConfirmationListener = + new ConfirmCancelOverlay.OnOverlayDismissedListener() { + @Override + public void onDismissed(boolean accepted) { + if (accepted) resetTheme(); + hideConfirmCancelOverlay(); + } + }; + + private View.OnClickListener mCustomizeResetClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (v == mDismissButton) { + hideCustomizeResetLayout(CustomizeResetAction.Dismiss); + } else if (v == mResetButton) { + hideCustomizeResetLayout(CustomizeResetAction.Reset); + } else if (v == mCustomizeButton) { + hideCustomizeResetLayout(CustomizeResetAction.Customize); + } + } + }; + + protected void loadComponentFromPackage(String pkgName, String component, long componentId) { + Bundle args = new Bundle(); + args.putString(ARG_PACKAGE_NAME, pkgName); + args.putLong(ARG_COMPONENT_ID, componentId); + int loaderId = LOADER_ID_INVALID; + if (MODIFIES_STATUS_BAR.equals(component)) { + loaderId = LOADER_ID_STATUS_BAR; + } else if (MODIFIES_FONTS.equals(component)) { + loaderId = LOADER_ID_FONT; + } else if (MODIFIES_ICONS.equals(component)) { + loaderId = LOADER_ID_ICONS; + } else if (MODIFIES_NAVIGATION_BAR.equals(component)) { + loaderId = LOADER_ID_NAVIGATION_BAR; + } else if (MODIFIES_LAUNCHER.equals(component)) { + if (pkgName != null) { + if (TextUtils.isEmpty(pkgName)) { + mWallpaperCard.setWallpaper(null); + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LAUNCHER, WALLPAPER_NONE); + setCardTitle(mWallpaperCard, WALLPAPER_NONE, + getString(R.string.wallpaper_label)); + getChooserActivity().showSaveApplyButton(); + } else if (ComponentSelector.EXTERNAL_WALLPAPER.equals(pkgName)) { + // Check if we have READ_EXTERNAL_STORAGE permission and if not request it, + // otherwise let the user pick an image + if (getActivity().checkSelfPermission( + READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { + mAfterPermissionGrantedRunnable = new Runnable() { + @Override + public void run() { + getChooserActivity().pickExternalWallpaper(); + setCardTitle(mWallpaperCard, WALLPAPER_NONE, + getString(R.string.wallpaper_label)); + } + }; + requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, + PERMISSION_REQUEST); + } else { + getChooserActivity().pickExternalWallpaper(); + setCardTitle(mWallpaperCard, WALLPAPER_NONE, + getString(R.string.wallpaper_label)); + } + } else { + loaderId = LOADER_ID_WALLPAPER; + } + } + } else if (MODIFIES_LOCKSCREEN.equals(component) + || MODIFIES_LIVE_LOCK_SCREEN.equals(component)) { + if (pkgName != null && TextUtils.isEmpty(pkgName)) { + mLockScreenCard.setWallpaper(null); + mSelectedComponentsMap.put(ThemesColumns.MODIFIES_LOCKSCREEN, LOCKSCREEN_NONE); + + if(mSelectedComponentsMap.containsKey(MODIFIES_LIVE_LOCK_SCREEN)) { + mSelectedComponentsMap.put(MODIFIES_LIVE_LOCK_SCREEN, LOCKSCREEN_NONE); + } + setCardTitle(mLockScreenCard, WALLPAPER_NONE, + getString(R.string.lockscreen_label)); + if (mLockScreenCard.isShowingEmptyView()) { + mLockScreenCard.setEmptyViewEnabled(false); + } + getChooserActivity().showSaveApplyButton(); + } else if (ComponentSelector.EXTERNAL_WALLPAPER.equals(pkgName)) { + // Check if we have READ_EXTERNAL_STORAGE permission and if not request it, + // otherwise let the user pick an image + if (getActivity().checkSelfPermission( + READ_EXTERNAL_STORAGE) != PERMISSION_GRANTED) { + mAfterPermissionGrantedRunnable = new Runnable() { + @Override + public void run() { + getChooserActivity().pickExternalLockscreen(); + setCardTitle(mLockScreenCard, WALLPAPER_NONE, + getString(R.string.lockscreen_label)); + } + }; + requestPermissions(new String[] {READ_EXTERNAL_STORAGE}, + PERMISSION_REQUEST); + } else { + getChooserActivity().pickExternalLockscreen(); + setCardTitle(mLockScreenCard, WALLPAPER_NONE, + getString(R.string.lockscreen_label)); + } + } else if (ComponentSelector.MOD_LOCK.equals(pkgName)) { + startLiveLockScreenSettings(); + } else { + if (MODIFIES_LIVE_LOCK_SCREEN.equals(component)) { + loaderId = LOADER_ID_LIVE_LOCK_SCREEN; + } else { + loaderId = LOADER_ID_LOCKSCREEN; + } + } + } else if (MODIFIES_OVERLAYS.equals(component)) { + loaderId = LOADER_ID_STYLE; + } else if (MODIFIES_BOOT_ANIM.equals(component)) { + loaderId = LOADER_ID_BOOT_ANIMATION; + } else if (MODIFIES_RINGTONES.equals(component)) { + loaderId = LOADER_ID_RINGTONE; + } else if (MODIFIES_NOTIFICATIONS.equals(component)) { + loaderId = LOADER_ID_NOTIFICATION; + } else if (MODIFIES_ALARMS.equals(component)) { + loaderId = LOADER_ID_ALARM; + } else { + return; + } + + if (loaderId != LOADER_ID_INVALID) { + getLoaderManager().restartLoader(loaderId, args, ThemeFragment.this); + } + } + + private OnItemClickedListener mOnComponentItemClicked = new OnItemClickedListener() { + @Override + public void onItemClicked(String pkgName, long componentId, Bundle params) { + String component = mSelector.getComponentType(); + if (MODIFIES_LOCKSCREEN.equals(component) && params != null) { + boolean isLiveLockView = params.getBoolean( + ComponentSelector.IS_LIVE_LOCK_SCREEN_VIEW,false); + if (isLiveLockView) { + //We got here because an live lock thubmnail view was clicked. We need to + //replace the component to load the proper data from the provider. + component = MODIFIES_LIVE_LOCK_SCREEN; + } + } + loadComponentFromPackage(pkgName, component, componentId); + } + }; + + private void fadeOutNonSelectedCards(int selectedCardId) { + for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { + if (mCardIdsToComponentTypes.keyAt(i) != selectedCardId) { + ComponentCardView card = (ComponentCardView) getView().findViewById( + mCardIdsToComponentTypes.keyAt(i)); + if (card != null) card.animateCardFadeOut(); + } + } + } + + protected void animateContentChange(int parentId, View viewToAnimate, Drawable overlay) { + ((ComponentCardView) getView().findViewById(parentId)) + .animateContentChange(viewToAnimate, overlay, ANIMATE_COMPONENT_CHANGE_DURATION); + } + + private Runnable mApplyThemeRunnable = new Runnable() { + @Override + public void run() { + final Context context = getActivity(); + if (context != null) { + // Post this on mHandler so the client is added and removed from the same + // thread + mHandler.post(new Runnable() { + @Override + public void run() { + final Map componentsToApply = getComponentsToApply(); + if (componentsToApply != null && componentsToApply.size() > 0) { + final Map fullMap + = fillMissingComponentsWithDefault(componentsToApply); + ThemeManager tm = getThemeManager(); + if (tm != null) { + try { + tm.addClient(ThemeFragment.this); + } catch (IllegalArgumentException e) { + /* ignore since this means we already have a listener added */ + } + ThemeChangeRequest request = + getThemeChangeRequestForComponents(fullMap); + boolean value = request.getReqeustType(). + equals(RequestType.USER_REQUEST_MIXNMATCH); + + tm.requestThemeChange(request, !value); + } + mApplyThemeOnPopulated = false; + } else { + onFinish(true); + } + } + }); + } + } + }; + + protected Map fillMissingComponentsWithDefault( + Map originalMap) { + HashMap newMap = new HashMap(); + newMap.putAll(originalMap); + Map defaultMap = getEmptyComponentsMap(); + for(Map.Entry entry : defaultMap.entrySet()) { + String component = entry.getKey(); + String defaultPkg = entry.getValue(); + if (!newMap.containsKey(component)) { + newMap.put(component, defaultPkg); + } + } + return newMap; + } + + protected Map getEmptyComponentsMap() { + List componentsList = ThemeUtils.getAllComponents(); + Map defaultMap = new HashMap<>(componentsList.size()); + for (String component : componentsList) { + defaultMap.put(component, ""); + } + return defaultMap; + } + + /** + * This is the method that will be called when applying a theme and the idea is to override + * it in MyThemeFragment and pass in a different RequestType, once we have a type that indicates + * the user is mixing and matching instead of applying an entire theme. + * @param componentMap + * @return + */ + protected ThemeChangeRequest getThemeChangeRequestForComponents( + Map componentMap) { + return getThemeChangeRequestForComponents(componentMap, RequestType.USER_REQUEST); + } + + protected ThemeChangeRequest getThemeChangeRequestForComponents( + Map componentMap, RequestType requestType) { + ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); + for (String component : componentMap.keySet()) { + builder.setComponent(component, componentMap.get(component)); + } + builder.setRequestType(requestType); + if (mThemeVersion >= 3) { + builder.setWallpaperId(mSelectedWallpaperComponentId != null + ? mSelectedWallpaperComponentId + : DEFAULT_COMPONENT_ID); + } + return builder.build(); + } + + protected Map getComponentsToApply() { + return mSelectedComponentsMap; + } + + private Runnable mApplyExternalWallpaperRunnable = new Runnable() { + @Override + public void run() { + // If an external image was selected for the wallpaper, we need to + // set that manually. + if (mExternalWallpaperUri != null) { + WallpaperManager wm = + WallpaperManager.getInstance(getActivity()); + final Context context = getActivity(); + final Resources res = context.getResources(); + final Point size = new Point(wm.getDesiredMinimumWidth(), + wm.getDesiredMinimumHeight()); + Bitmap bmp = WallpaperUtils.createPreview(size, context, mExternalWallpaperUri, + null, res, 0, 0, false); + try { + wm.setBitmap(bmp); + } catch (Exception e) { + Log.e(TAG, "Unable to set external wallpaper", e); + } + } + } + }; + + private Runnable mApplyExternalLockscreenRunnable = new Runnable() { + @Override + public void run() { + // If an external image was selected for the wallpaper, we need to + // set that manually. + if (mExternalLockscreenUri != null) { + WallpaperManager wm = + WallpaperManager.getInstance(getActivity()); + final Context context = getActivity(); + final Resources res = context.getResources(); + final Point size = new Point(); + ((Activity) context).getWindowManager().getDefaultDisplay().getRealSize(size); + Bitmap bmp = WallpaperUtils.createPreview(size, context, mExternalLockscreenUri, + null, res, 0, 0, false); + try { + wm.setKeyguardBitmap(bmp); + } catch (Exception e) { + Log.e(TAG, "Unable to set external lockscreen wallpaper", e); + } + } + } + }; + + class RestoreLockScreenCardRunnable implements Runnable { + + final private boolean mWasShowingNone; + private String mCurrentLockScreenPkgName; + + public RestoreLockScreenCardRunnable(boolean w, String pkgName) { + mWasShowingNone = w; + mCurrentLockScreenPkgName = pkgName; + } + + @Override + public void run() { + if (!TextUtils.isEmpty(mCurrentLockScreenPkgName)) { + loadComponentFromPackage(mCurrentLockScreenPkgName, + MODIFIES_LOCKSCREEN, 0); + } else if (mWasShowingNone) { + mLockScreenCard.setWallpaper(null); + TextView none = (TextView) mLockScreenCard.findViewById( + R.id.none); + if (none != null) { + none.setVisibility(View.VISIBLE); + } + mLockScreenCard.setEmptyViewEnabled(false); + } else { + mLockScreenCard.clearWallpaper(); + TextView none = (TextView) mLockScreenCard.findViewById( + R.id.none); + if (none != null) { + none.setVisibility(View.GONE); + } + mLockScreenCard.setEmptyViewEnabled(true); + setAddComponentTitle(mLockScreenCard, getString(R.string.lockscreen_label)); + } + } + } + + protected void applyTheme() { + if (mExternalWallpaperUri == null && mExternalLockscreenUri == null && + (mSelectedComponentsMap == null || mSelectedComponentsMap.size() <= 0)) { + return; + } + final Map componentsToApply = getComponentsToApply(); + boolean isLLSEnabled = CMSettings.Secure.getInt(getActivity().getContentResolver(), + LIVE_LOCK_SCREEN_ENABLED, 0) == 1; + if (!TextUtils.isEmpty(componentsToApply.get(MODIFIES_LIVE_LOCK_SCREEN)) && !isLLSEnabled) { + AlertDialog d = new AlertDialog.Builder(getActivity(), + android.R.style.Theme_Material_Dialog) + .setTitle(R.string.enable_live_lock_screen_dialog_title) + .setMessage(R.string.enable_live_lock_screen_dialog_message) + .setPositiveButton(R.string.enable_live_lock_screen_dialog_positive_btn_text, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + CMSettings.Secure.putInt(getActivity().getContentResolver(), + LIVE_LOCK_SCREEN_ENABLED, 1); + getChooserActivity().themeChangeStart(); + animateProgressIn(mApplyThemeRunnable); + } + }) + .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mSelectedComponentsMap.remove(MODIFIES_LIVE_LOCK_SCREEN); + boolean wasNone = false; + if (TextUtils.equals("", mSelectedComponentsMap.get( + MODIFIES_LOCKSCREEN))) { + if (!TextUtils.isEmpty(mCurrentTheme.get(MODIFIES_LOCKSCREEN))) { + //The map entry was set to empty string because there's a + //lockscreen currently applied and setting this entry to empty + //would instruct the ThemeManager to clear the currently applied + //lockscreen, but the user decided not to enable LLS so we need + //to abort this change + mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, + mCurrentTheme.get(MODIFIES_LOCKSCREEN)); + } else { + wasNone = true; + } + } + //Restore the lockscreen card to its previous state + mHandler.post(new RestoreLockScreenCardRunnable(wasNone, + mCurrentTheme.get(MODIFIES_LOCKSCREEN))); + + //Did the user make more changes? + boolean modLLS = componentsToApply.containsKey( + MODIFIES_LIVE_LOCK_SCREEN); + boolean modLockscreen = componentsToApply.containsKey( + MODIFIES_LOCKSCREEN); + if (modLLS && ((modLockscreen && componentsToApply.size() > 2) || + (!modLockscreen && componentsToApply.size() > 1))) { + getChooserActivity().themeChangeStart(); + animateProgressIn(mApplyThemeRunnable); + } + } + }) + .setCancelable(false) + .create(); + d.setCanceledOnTouchOutside(false); + d.show(); + } else { + getChooserActivity().themeChangeStart(); + animateProgressIn(mApplyThemeRunnable); + } + } + + /** + * Use when applyTheme() might be too early. ie mSelectedComponentsMap is not pop. yet + * @param pkgName Only used in MyThemeFragment to apply components on top of current theme + * @param components Optional list of components to apply. + */ + protected void applyThemeWhenPopulated(String pkgName, List components) { + mApplyThemeOnPopulated = true; + } + + private void animateProgressIn(Runnable endAction) { + mProgress.setVisibility(View.VISIBLE); + mProgress.setProgress(0); + float pivotX = mTitleLayout.getWidth() - + getResources().getDimensionPixelSize(R.dimen.apply_progress_padding); + ScaleAnimation scaleAnim = new ScaleAnimation(0f, 1f, 1f, 1f, + pivotX, 0f); + scaleAnim.setDuration(ANIMATE_PROGRESS_IN_DURATION); + + mTitleLayout.animate() + .translationXBy(-(pivotX / 3)) + .alpha(0f) + .setDuration(ANIMATE_TITLE_OUT_DURATION) + .setInterpolator(new AccelerateInterpolator()) + .withEndAction(endAction).start(); + mProgress.startAnimation(scaleAnim); + } + + private void animateProgressOut() { + mProgress.setVisibility(View.VISIBLE); + float pivotX = mTitleLayout.getWidth() - + getResources().getDimensionPixelSize(R.dimen.apply_progress_padding); + ScaleAnimation scaleAnim = new ScaleAnimation(1f, 0f, 1f, 1f, + pivotX, 0f); + scaleAnim.setDuration(ANIMATE_PROGRESS_OUT_DURATION); + scaleAnim.setFillAfter(false); + scaleAnim.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + mProgress.setVisibility(View.GONE); + if (mThemeResetting) { + mThemeResetting = false; + mThemeTagLayout.setCustomizedTagEnabled(false); + } + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + + mTitleLayout.animate() + .translationXBy((pivotX / 3)) + .alpha(1f) + .setDuration(ANIMATE_TITLE_IN_DURATION) + .setInterpolator(new AccelerateInterpolator()) + .start(); + mProgress.startAnimation(scaleAnim); + if (mThemeResetting) mReset.setVisibility(View.GONE); + } + + private void animateContentIn() { + if (mSkipLoadingAnim) { + return; + } + AnimatorSet set = new AnimatorSet(); + set.setDuration(ANIMATE_TITLE_IN_DURATION); + set.play(ObjectAnimator.ofFloat(mLoadingView, "alpha", 1f, 0f)) + .with(ObjectAnimator.ofFloat(mTitleLayout, "alpha", 0f, 1f)); + set.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mLoadingView.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + set.start(); + } + + private void disableActionButtons() { + mCustomize.setEnabled(false); + mDelete.setEnabled(false); + mReset.setEnabled(false); + } + + private void enableActionButtons() { + mCustomize.setEnabled(true); + mDelete.setEnabled(true); + mReset.setEnabled(true); + } + + public boolean isShowingConfirmCancelOverlay() { + return mConfirmCancelOverlay.getVisibility() == View.VISIBLE; + } + + public void showApplyThemeOverlay() { + if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; + mConfirmCancelOverlay.setTitle(R.string.apply_theme_overlay_title); + mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() + .getColor(R.color.apply_overlay_background)); + mConfirmCancelOverlay.setOnOverlayDismissedListener(mApplyCancelListener); + getChooserActivity().lockPager(); + ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); + mConfirmCancelOverlay.setVisibility(View.VISIBLE); + mConfirmCancelOverlay.setAlpha(0f); + anim.setListener(null); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(1f).start(); + + if (mIsLegacyTheme) { + // Display cm11 theme warning message + TextView tv = (TextView) mConfirmCancelOverlay.findViewById(R.id.warning_message); + tv.setVisibility(View.VISIBLE); + tv.setText(String.format(getString(R.string.legacy_theme_warning), mTitle.getText())); + } else if (Utils.hasPerAppThemesApplied(getActivity())) { + // Display per app theme changes will be removed warning + TextView tv = (TextView) mConfirmCancelOverlay.findViewById(R.id.warning_message); + tv.setVisibility(View.VISIBLE); + tv.setText(String.format(getString(R.string.per_app_theme_removal_warning), + mTitle.getText())); + } + + disableActionButtons(); + mClickableView.setSoundEffectsEnabled(false); + } + + public void showDeleteThemeOverlay() { + if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; + mConfirmCancelOverlay.setTitle(R.string.delete_theme_overlay_title); + mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() + .getColor(R.color.delete_overlay_background)); + mConfirmCancelOverlay.setOnOverlayDismissedListener(mDeleteConfirmationListener); + getChooserActivity().lockPager(); + ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); + mConfirmCancelOverlay.setVisibility(View.VISIBLE); + mConfirmCancelOverlay.setAlpha(0f); + anim.setListener(null); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(1f).start(); + + disableActionButtons(); + mClickableView.setSoundEffectsEnabled(false); + } + + public void showResetThemeOverlay() { + if (mConfirmCancelOverlay.getVisibility() == View.VISIBLE) return; + mConfirmCancelOverlay.setTitle(R.string.reset_theme_overlay_title); + mConfirmCancelOverlay.setBackgroundColor(getActivity().getResources() + .getColor(R.color.apply_overlay_background)); + mConfirmCancelOverlay.setOnOverlayDismissedListener(mResetConfirmationListener); + getChooserActivity().lockPager(); + ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); + mConfirmCancelOverlay.setVisibility(View.VISIBLE); + mConfirmCancelOverlay.setAlpha(0f); + anim.setListener(null); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(1f).start(); + + disableActionButtons(); + mClickableView.setSoundEffectsEnabled(false); + } + + public void hideConfirmCancelOverlay() { + hideConfirmCancelOverlay(false); + } + + /** + * Hides the apply theme layout overlay and can apply the selected theme + * when the animation is finished. + * @param applyThemeWhenFinished If true, the current theme will be applied. + */ + private void hideConfirmCancelOverlay(final boolean applyThemeWhenFinished) { + getChooserActivity().unlockPager(); + ViewPropertyAnimator anim = mConfirmCancelOverlay.animate(); + mConfirmCancelOverlay.setVisibility(View.VISIBLE); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(0f).start(); + anim.setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mConfirmCancelOverlay.setVisibility(View.GONE); + if (applyThemeWhenFinished) applyTheme(); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + + enableActionButtons(); + mClickableView.setSoundEffectsEnabled(true); + } + + public boolean isShowingCustomizeResetLayout() { + return mCustomizeResetLayout.getVisibility() == View.VISIBLE; + } + + public void showCustomizeResetLayout() { + if (mCustomizeResetLayout.getVisibility() == View.VISIBLE) return; + if (!mThemeTagLayout.isCustomizedTagEnabled()) { + mResetButton.setEnabled(false); + } else { + mResetButton.setEnabled(true); + } + getChooserActivity().lockPager(); + ViewPropertyAnimator anim = mCustomizeResetLayout.animate(); + mCustomizeResetLayout.setVisibility(View.VISIBLE); + mCustomizeResetLayout.setAlpha(0f); + anim.setListener(null); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(1f).start(); + + disableActionButtons(); + mClickableView.setSoundEffectsEnabled(false); + } + + public void hideCustomizeResetLayout() { + hideCustomizeResetLayout(CustomizeResetAction.Dismiss); + } + + private void hideCustomizeResetLayout(final CustomizeResetAction action) { + getChooserActivity().unlockPager(); + ViewPropertyAnimator anim = mCustomizeResetLayout.animate(); + mCustomizeResetLayout.setVisibility(View.VISIBLE); + anim.setDuration(ANIMATE_APPLY_LAYOUT_DURATION); + anim.alpha(0f).start(); + anim.setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + mCustomizeResetLayout.setVisibility(View.GONE); + switch (action) { + case Customize: + getChooserActivity().expand(); + break; + case Reset: + resetTheme(); + break; + } + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + + enableActionButtons(); + mClickableView.setSoundEffectsEnabled(true); + } + + public void showThemeTagLayout() { + mThemeTagLayout.setVisibility(View.VISIBLE); + mThemeTagLayout.animate().alpha(1f).setStartDelay(ANIMATE_START_DELAY).start(); + } + + public void hideThemeTagLayout() { + mThemeTagLayout.setAlpha(0f); + mThemeTagLayout.setVisibility(View.GONE); + } + + public void hideProcessingOverlay() { + mProcessingThemeLayout.animate().alpha(0).withEndAction(new Runnable() { + @Override + public void run() { + mProcessingThemeLayout.setVisibility(View.GONE); + } + }).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); + mCustomize.setVisibility(View.VISIBLE); + mCustomize.setAlpha(0f); + mCustomize.animate().alpha(1f).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); + if (mDelete.getVisibility() != View.GONE) { + mDelete.setVisibility(View.VISIBLE); + mDelete.setAlpha(0f); + mDelete.animate().alpha(1f).setDuration(ANIMATE_APPLY_LAYOUT_DURATION).start(); + } + + enableActionButtons(); + mClickableView.setSoundEffectsEnabled(true); + } + + public void fadeInCards() { + for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) { + final int key = mCardIdsToComponentTypes.keyAt(i); + if (key != mActiveCardId) { + ComponentCardView card = (ComponentCardView) getView().findViewById(key); + if (card != null) card.animateCardFadeIn(); + } + } + mActiveCardId = -1; + } + + public boolean componentsChanged() { + // If an external wallpaper/ls are set then something changed! + if (mExternalWallpaperUri != null || mExternalLockscreenUri != null) return true; + + for (String key : mSelectedComponentsMap.keySet()) { + if (!mPkgName.equals(mSelectedComponentsMap.get(key))) { + return true; + } + if (ThemesColumns.MODIFIES_LAUNCHER.equals(key) && + mCurrentWallpaperComponentId.value != mSelectedWallpaperComponentId) { + return true; + } + } + return false; + } + + protected boolean isThemeCustomized() { + final String themePkgName = getThemePackageName(); + for (String key : mSelectedComponentsMap.keySet()) { + final String selectedPkgName = mSelectedComponentsMap.get(key); + if (!themePkgName.equals(selectedPkgName)) { + return true; + } + if (mBaseThemeSupportedComponents.size() > 0 && + !mBaseThemeSupportedComponents.contains(key)) { + return true; + } + } + // finally check if we're missing anything from mBaseThemeSupportedComponents + for (String component : mBaseThemeSupportedComponents) { + if (!mSelectedComponentsMap.containsKey(component)) return true; + } + return false; + } + + public void clearChanges() { + mSelectedComponentsMap.clear(); + mExternalWallpaperUri = null; + mExternalLockscreenUri = null; + View none = mLockScreenCard.findViewById(R.id.none); + if (none != null && none.getVisibility() == View.VISIBLE) { + none.setVisibility(View.GONE); + } + TextView tv = (TextView) mLockScreenCard.findViewById(R.id.label); + if (tv != null) { + tv.setAlpha(1f); + tv.setBackgroundResource(R.drawable.wallpaper_label_bg); + } + getLoaderManager().restartLoader(LOADER_ID_ALL, null, ThemeFragment.this); + } + + public String getThemePackageName() { + if (mPkgName == null) { + // check if the package name is defined in the arguments bundle + Bundle bundle = getArguments(); + if (bundle != null) { + mPkgName = bundle.getString(ARG_PACKAGE_NAME); + } + } + return mPkgName; + } + + private void uninstallTheme() { + getChooserActivity().uninstallTheme(mPkgName); + } + + public void setCurrentTheme(Map currentTheme, + MutableLong currentWallpaperComponentId) { + mCurrentTheme = currentTheme; + mCurrentWallpaperComponentId = currentWallpaperComponentId; + } + + /** + * Slides the scrollview content up and adds a space view at the bottom + * of mAdditionalCards so all content can be visible above the selector. + * + * We are using a ValueAnimator here to scroll the content rather than calling + * mScrollView.smoothScrollBy() since the speed of that animation cannot be customized. + * @param yDelta + * @param selectorHeight + */ + public void slideContentIntoView(final int yDelta, int selectorHeight) { + Space space = (Space) mAdditionalCards.findViewById(ADDITIONAL_CONTENT_SPACE_ID); + if (space == null) { + // No space view yet so lets create it one + space = new Space(getActivity()); + space.setId(ADDITIONAL_CONTENT_SPACE_ID); + mAdditionalCards.addView(space, + new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, + selectorHeight)); + } else { + // Space view already exists so just update the LayoutParams + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) space.getLayoutParams(); + params.height = selectorHeight; + space.setLayoutParams(params); + } + final int startY = mScrollView.getScrollY(); + final ValueAnimator scrollAnimator = + ValueAnimator.ofInt(startY, startY + yDelta); + scrollAnimator.setDuration(SLIDE_CONTENT_ANIM_DURATION); + scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + mScrollView.scrollTo(0, value); + } + }); + scrollAnimator.start(); + } + + public Map getSelectedComponentsMap() { + return mSelectedComponentsMap; + } + + /** + * Slides the scrollview content down and removes a space view at the bottom + * of mAdditionalCards. + * + * We are using a ValueAnimator here to scroll the content rather than calling + * mScrollView.smoothScrollBy() since the speed of that animation cannot be customized. + * @param yDelta + */ + public void slideContentBack(int yDelta) { + final int startY = mScrollView.getScrollY(); + final ValueAnimator scrollAnimator = + ValueAnimator.ofInt(startY, startY + yDelta); + scrollAnimator.setDuration(SLIDE_CONTENT_ANIM_DURATION); + scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + mScrollView.scrollTo(0, value); + } + }); + scrollAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + View space = mAdditionalCards.findViewById(ADDITIONAL_CONTENT_SPACE_ID); + if (space != null) mAdditionalCards.removeView(space); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + scrollAnimator.start(); + } + + public void showLockScreenCard() { + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + final int scrollToY = mStatusBarCard.getMeasuredHeight() + + mFontCard.getMeasuredHeight() + mIconCard.getMeasuredHeight() + + mNavBarCard.getMeasuredHeight() + mWallpaperCard.getMeasuredHeight() / 2; + final ValueAnimator scrollAnimator = ValueAnimator.ofInt(0, scrollToY); + scrollAnimator.setDuration(LOCK_SCREEN_CARD_SCROLL_ANIMATION_DURATION); + scrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (Integer) animation.getAnimatedValue(); + mScrollView.scrollTo(0, value); + } + }); + scrollAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + mCardClickListener.onClick(mLockScreenCard); + } + }); + scrollAnimator.start(); + } + }, SHOW_LOCK_SCREEN_CARD_DELAY); + } + + protected void startLiveLockScreenSettings() { + Intent intent = new Intent(cyanogenmod.content.Intent.ACTION_OPEN_LIVE_LOCKSCREEN_SETTINGS); + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + // TODO: inform user that this action failed (Toast?) + } + } + + class AnimationLoader extends AsyncTask { + Context mContext; + String mPkgName; + BootAniImageView mBootAnim; + + public AnimationLoader(Context context, String pkgName, BootAniImageView bootAnim) { + mContext = context; + mPkgName = pkgName; + mBootAnim = bootAnim; + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + } + + @Override + protected Boolean doInBackground(Void... params) { + if (mContext == null) { + return Boolean.FALSE; + } + ZipFile zip = null; + if (ThemeConfig.SYSTEM_DEFAULT.equals(mPkgName)) { + try { + zip = new ZipFile(new File(BootAnimationHelper.SYSTEM_BOOT_ANI_PATH)); + } catch (Exception e) { + Log.w(TAG, "Unable to load boot animation", e); + return Boolean.FALSE; + } + } else { + // check if the bootanimation is cached + File f = new File(mContext.getCacheDir(), + mPkgName + BootAnimationHelper.CACHED_SUFFIX); + if (!f.exists()) { + // go easy on cache storage and clear out any previous boot animations + BootAnimationHelper.clearBootAnimationCache(mContext); + try { + Context themeContext = mContext.createPackageContext(mPkgName, 0); + AssetManager am = themeContext.getAssets(); + InputStream is = am.open("bootanimation/bootanimation.zip"); + FileUtils.copyToFile(is, f); + is.close(); + } catch (Exception e) { + Log.w(TAG, "Unable to load boot animation", e); + return Boolean.FALSE; + } + } + try { + zip = new ZipFile(f); + } catch (IOException e) { + Log.w(TAG, "Unable to load boot animation", e); + return Boolean.FALSE; + } + } + if (zip != null) { + mBootAnim.setBootAnimation(zip); + } else { + return Boolean.FALSE; + } + return Boolean.TRUE; + } + + @Override + protected void onPostExecute(Boolean isSuccess) { + super.onPostExecute(isSuccess); + if (isSuccess) { + mBootAnim.start(); + } + } + } + + class AudibleLoadingThread extends Thread { + private Context mContext; + private int mType; + private String mPkgName; + private MediaPlayer mPlayer; + + public AudibleLoadingThread(Context context, int type, String pkgName, MediaPlayer mp) { + super(); + mContext = context; + mType = type; + mPkgName = pkgName; + mPlayer = mp; + } + + @Override + public void run() { + try { + AudioUtils.loadThemeAudible(mContext, mType, mPkgName, mPlayer); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to load sound for " + mPkgName, e); + } + } + } +} diff --git a/src/org/cyanogenmod/theme/chooser/WallpaperCardView.java b/src/org/cyanogenmod/theme/chooser/WallpaperCardView.java new file mode 100644 index 0000000..a3ed8ea --- /dev/null +++ b/src/org/cyanogenmod/theme/chooser/WallpaperCardView.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.chooser; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewOverlay; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +public class WallpaperCardView extends ComponentCardView { + protected ImageView mImage; + protected TextView mLabel; + + public WallpaperCardView(Context context) { + this(context, null); + } + + public WallpaperCardView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public WallpaperCardView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WallpaperCardView); + String labelText = a.getString(R.styleable.WallpaperCardView_labelText); + a.recycle(); + + setOrientation(VERTICAL); + + setBackgroundResource(R.drawable.card_bg); + + LayoutInflater inflater = LayoutInflater.from(mContext); + FrameLayout frameLayout = + (FrameLayout) inflater.inflate(R.layout.wallpaper_card, this, false); + addView(frameLayout); + mLabel = (TextView) frameLayout.findViewById(R.id.label); + mImage = (ImageView) frameLayout.findViewById(R.id.image); + + mLabel.setText(labelText); + } + + public void setWallpaper(Drawable drawable) { + mImage.setImageDrawable(drawable); + View none = findViewById(R.id.none); + if (drawable == null) { + setBackgroundResource(R.drawable.card_wallpapertoggled_bg); + if (none != null) { + none.setVisibility(View.VISIBLE); + } + if (mLabel != null) { + mLabel.setBackgroundResource(0); + } + } else { + setBackgroundResource(0); + + if (none != null) { + none.setVisibility(View.GONE); + } + if (mLabel != null) { + mLabel.setBackgroundResource(R.drawable.wallpaper_label_bg); + } + } + } + + public void clearWallpaper() { + mImage.setImageDrawable(null); + setBackgroundResource(R.drawable.card_bg); + } + + public Drawable getWallpaperDrawable() { + return mImage.getDrawable(); + } + + @Override + public void expand(boolean showLabel) { + setEnabled(true); + } + + @Override + public void collapse() { + setEnabled(false); + } + + /** + * Animates a change in the content of the card + * @param v View in card to animate + * @param overlay Drawable to animate as a ViewOverlay + * @param duration Duration of animation + */ + @Override + public void animateContentChange(View v, final Drawable overlay, long duration) { + // Since the wallpaper IS the content, we will ignore the view passed in and animate + // the entire card + final ViewOverlay viewOverlay = this.getOverlay(); + viewOverlay.add(overlay); + final int x = 0; + final int y = 0; + final int width = v.getWidth(); + final int height = v.getHeight(); + overlay.setBounds(x, y, x + width, y + height); + + final ValueAnimator overlayAnimator = ValueAnimator.ofFloat(1f, 0f); + overlayAnimator.setDuration(duration); + overlayAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (Float) animation.getAnimatedValue(); + overlay.setAlpha((int) (255 * value)); + } + + }); + overlayAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + // Clear out the ViewOverlay now that we are done animating + viewOverlay.clear(); + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }); + + AnimatorSet set = new AnimatorSet(); + set.play(ObjectAnimator.ofFloat(overlay, "alpha", 0f, 1f)) + .with(overlayAnimator); + set.start(); + } +} diff --git a/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListLayout.java b/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListLayout.java new file mode 100644 index 0000000..424d640 --- /dev/null +++ b/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListLayout.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.perapptheming; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Canvas; +import android.graphics.Path; +import android.graphics.PointF; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.Animation; +import android.widget.FrameLayout; + +import org.cyanogenmod.theme.chooser.R; + +public class PerAppThemeListLayout extends FrameLayout { + private PerAppThemingWindow mWindow; + private PointF mCenter; + + private float mMaxRadius; + private float mTargetRadius; + private float mStartRadius; + private float mCurrentRadius; + + private ValueAnimator mAnimator; + private boolean mIsAnimating; + + private Path mRevealPath; + + public PerAppThemeListLayout(Context context) { + this(context, null); + } + + public PerAppThemeListLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PerAppThemeListLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + final Resources res = getResources(); + float width = res.getDimension(R.dimen.theme_list_width); + float height = res.getDimension(R.dimen.theme_list_max_height); + mMaxRadius = (float) Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + mRevealPath = new Path(); + + mAnimator = new ValueAnimator(); + mAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); + mAnimator.addListener(mAnimationListener); + mAnimator.addUpdateListener(mUpdateListener); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && + event.getKeyCode() == KeyEvent.KEYCODE_BACK && mWindow != null) { + mWindow.hideThemeList(); + } + return super.dispatchKeyEvent(event); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (isEnabled() && event.getAction() == MotionEvent.ACTION_DOWN && mWindow != null) { + mWindow.hideThemeList(); + return true; + } + return super.onTouchEvent(event); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + if (!mIsAnimating) { + super.dispatchDraw(canvas); + } else { + final int state = canvas.save(); + mRevealPath.reset(); + mRevealPath.addCircle(mCenter.x, mCenter.y, mCurrentRadius, Path.Direction.CW); + canvas.clipPath(mRevealPath); + super.dispatchDraw(canvas); + canvas.restoreToCount(state); + } + } + + public void setPerAppThemingWindow(PerAppThemingWindow window) { + mWindow = window; + } + + /** + * Perform a circular reveal from center cx,cy + * @param cx X position of center + * @param cy Y position of center + * @param duration Duration of animation + */ + public void circularReveal(float cx, float cy, long duration) { + mCenter = new PointF(cx, cy); + mIsAnimating = true; + + mStartRadius = mCurrentRadius; + mTargetRadius = mMaxRadius; + startAnimation(duration); + } + + /** + * Perform a circular hide from center cx,cy + * @param cx X position of center + * @param cy Y position of center + * @param duration Duration of animation + */ + public void circularHide(float cx, float cy, long duration) { + mCenter = new PointF(cx, cy); + mIsAnimating = true; + + mStartRadius = mCurrentRadius; + mTargetRadius = 0f; + startAnimation(duration); + } + + private void startAnimation(long duration) { + getChildAt(0).setVisibility(View.VISIBLE); + mAnimator.setFloatValues(mStartRadius, mTargetRadius); + mAnimator.setDuration(duration); + mAnimator.start(); + } + + private ValueAnimator.AnimatorUpdateListener mUpdateListener = + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + Float value = (Float) animation.getAnimatedValue(); + mCurrentRadius = value.floatValue(); + invalidate(); + } + }; + + private Animator.AnimatorListener mAnimationListener = new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) {} + + @Override + public void onAnimationEnd(Animator animation) { + mIsAnimating = false; + if (mCurrentRadius <= 0) { + getChildAt(0).setVisibility(INVISIBLE); + } + } + + @Override + public void onAnimationCancel(Animator animation) {} + + @Override + public void onAnimationRepeat(Animator animation) {} + }; +} diff --git a/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListView.java b/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListView.java new file mode 100644 index 0000000..9986f60 --- /dev/null +++ b/src/org/cyanogenmod/theme/perapptheming/PerAppThemeListView.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.perapptheming; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.ListView; + +import org.cyanogenmod.theme.chooser.R; +import org.cyanogenmod.theme.util.Utils; + +public class PerAppThemeListView extends ListView { + private int mMinHeight; + private int mMaxHeight; + + public PerAppThemeListView(Context context) { + this(context, null); + } + + public PerAppThemeListView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PerAppThemeListView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + final Resources res = getResources(); + TypedArray a = context.obtainStyledAttributes(attrs, + Utils.getResourceDeclareStyleableIntArray("com.android.internal", "View")); + int resId = res.getIdentifier("View_minHeight", "styleable", "android"); + mMinHeight = a.getDimensionPixelSize(resId, 0); + a.recycle(); + + a = context.obtainStyledAttributes(attrs, R.styleable.PerAppThemeListView); + mMaxHeight = a.getDimensionPixelSize(R.styleable.PerAppThemeListView_maxHeight, 0); + a.recycle(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // let the super do the heavy lifting and then we'll cap the values to any max and/or min + // values that were defined in the layout + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int measuredWidth = getMeasuredWidth(); + int measuredHeight = getMeasuredHeight(); + int newHeight = measuredHeight; + if (mMaxHeight > 0) { + newHeight = Math.min(measuredHeight, mMaxHeight); + } + if (mMinHeight > 0) { + newHeight = Math.max(newHeight, mMinHeight); + } + if (newHeight != measuredHeight) { + setMeasuredDimension(measuredWidth, newHeight); + } + } +} diff --git a/src/org/cyanogenmod/theme/perapptheming/PerAppThemingWindow.java b/src/org/cyanogenmod/theme/perapptheming/PerAppThemingWindow.java new file mode 100644 index 0000000..27db329 --- /dev/null +++ b/src/org/cyanogenmod/theme/perapptheming/PerAppThemingWindow.java @@ -0,0 +1,1078 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.perapptheming; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.database.ContentObserver; +import android.database.Cursor; +import android.graphics.PixelFormat; +import android.net.Uri; +import android.os.Handler; +import android.os.IBinder; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.WindowManager; +import android.view.animation.Interpolator; +import android.view.animation.OvershootInterpolator; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import org.cyanogenmod.theme.chooser.R; +import org.cyanogenmod.theme.util.Utils; + +import cyanogenmod.providers.ThemesContract.ThemesColumns; +import cyanogenmod.themes.ThemeChangeRequest; +import cyanogenmod.themes.ThemeManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +public class PerAppThemingWindow extends Service implements OnTouchListener, + ThemeManager.ThemeChangeListener { + // Animation frame rate per second + private static final int ANIMATION_FRAME_RATE = 60; + + private static final int EXIT_DELETE_MODE_ANIMATION_DURATION = 50; + + private static final int MOVE_TO_DELETE_BOX_ANIMATION_DURATION = 150; + + private static final int ANIMATION_DURATION = 300; + + private static final int FAB_SCALE_ANIMATION_DURATION = 150; + + private static final int LIST_ON_LEFT_SIDE = 0; + private static final int LIST_ON_RIGHT_SIDE = 1; + + // Don't want these colors to be themable and possibly alter the effect we are after, so + // they are defined here rather than in colors.xml + private static final int SCRIM_COLOR_TRANSPARENT = 0x00000000; + private static final int SCRIM_COLOR_OPAQUE = 0xaa000000; + + // Amount to wait after a theme change occurred before fading the scrim away + // This value was obtained empirically by performing theme changes and adjusting this delay + private static final int THEME_CHANGE_DELAY = 1500; + + private static final float PRESSED_FAB_SCALE = 0.95f; + + private static final float DELETE_BOX_ANIMATION_SCALE = 0.3f; + + private static final int MAX_DEPRECIATION = 5; + + private static final float FAB_ANIMATION_SCALE_FACTOR = 0.44f; + + // Margin around the phone + private static int MARGIN_VERTICAL; + // Margin around the phone + private static int MARGIN_HORIZONTAL; + private static int CLOSE_ANIMATION_DISTANCE; + private static int DRAG_DELTA; + private static int STARTING_POINT_Y; + private static int DELETE_BOX_WIDTH; + private static int DELETE_BOX_HEIGHT; + private static int FLOATING_WINDOW_ICON_SIZE; + + // View variables + private BroadcastReceiver mBroadcastReceiver; + private WindowManager mWindowManager; + private LinearLayout mDraggableIcon; + private View mDraggableIconImage; + private WindowManager.LayoutParams mParams; + private PerAppThemeListLayout mThemeListLayout; + private WindowManager.LayoutParams mListLayoutParams; + private ListView mThemeList; + private ThemesAdapter mAdapter; + private FrameLayout.LayoutParams mListParams; + private LinearLayout mDeleteView; + private View mDeleteBoxView; + private View mThemeApplyingView; + private boolean mDeleteBoxVisible = false; + private boolean mIsDestroyed = false; + private boolean mIsBeingDestroyed = false; + private int mCurrentPosX = -1; + + // Animation variables + private List mDeltaXArray; + private List mDeltaYArray; + private AnimationTask mAnimationTask; + + // Close logic + private int mCurrentX; + private int mCurrentY; + private boolean mIsInDeleteMode = false; + private boolean mIsAnimationLocked = false; + + // Drag variables + float mPrevDragX; + float mPrevDragY; + float mOrigX; + float mOrigY; + boolean mDragged; + + private int mListSide = LIST_ON_LEFT_SIDE; + + private ThemeConfig mThemeConfig; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + // Load margins, distances, etc. + final Resources res = getResources(); + MARGIN_VERTICAL = + res.getDimensionPixelSize(R.dimen.floating_window_margin_vertical); + MARGIN_HORIZONTAL = + res.getDimensionPixelSize(R.dimen.floating_window_margin_horizontal); + CLOSE_ANIMATION_DISTANCE = + res.getDimensionPixelSize(R.dimen.floating_window_close_animation_distance); + DRAG_DELTA = res.getDimensionPixelSize(R.dimen.floating_window_drag_delta); + STARTING_POINT_Y = res.getDimensionPixelSize(R.dimen.floating_window_starting_point_y); + + DELETE_BOX_WIDTH = (int) getResources().getDimension( + R.dimen.floating_window_delete_box_width); + DELETE_BOX_HEIGHT = (int) getResources().getDimension( + R.dimen.floating_window_delete_box_height); + FLOATING_WINDOW_ICON_SIZE = (int) getResources().getDimension( + R.dimen.floating_window_icon); + + mDeleteView = new LinearLayout(getContext()); + View.inflate(getContext(), R.layout.per_app_delete_box_window, mDeleteView); + mDeleteBoxView = mDeleteView.findViewById(R.id.box); + addView(mDeleteView, 0, 0, Gravity.BOTTOM | Gravity.CENTER_VERTICAL, + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.WRAP_CONTENT); + mDeleteView.setVisibility(View.GONE); + + mDraggableIcon = new LinearLayout(this); + mDraggableIcon.setOnTouchListener(this); + View.inflate(getContext(), R.layout.per_app_fab_floating_window_icon, mDraggableIcon); + mDraggableIconImage = mDraggableIcon.findViewById(R.id.box); + mDraggableIconImage.setClipToOutline(true); + mDraggableIconImage.getViewTreeObserver().addOnWindowAttachListener(mWindowAttachListener); + mParams = addView(mDraggableIcon, 0, 0); + updateIconPosition(MARGIN_HORIZONTAL, STARTING_POINT_Y); + + mThemeListLayout = (PerAppThemeListLayout) View.inflate(getContext(), + R.layout.per_app_theme_list, null); + mThemeListLayout.setPerAppThemingWindow(this); + mThemeList = (ListView) mThemeListLayout.findViewById(R.id.theme_list); + mListParams = (FrameLayout.LayoutParams) mThemeList.getLayoutParams(); + mThemeApplyingView = mThemeListLayout.findViewById(R.id.applying_theme_text); + + final Configuration config = getResources().getConfiguration(); + mThemeConfig = getThemeConfig(config); + loadThemes(); + getContentResolver().registerContentObserver(ThemesColumns.CONTENT_URI, true, + mThemesObserver); + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mIsBeingDestroyed) return true; + + switch(event.getAction()) { + case MotionEvent.ACTION_DOWN: + if (mThemeListLayout.isAttachedToWindow()) { + hideThemeList(); + return false; + } + mPrevDragX = mOrigX = event.getRawX(); + mPrevDragY = mOrigY = event.getRawY(); + + mDragged = false; + + mDeltaXArray = new LinkedList(); + mDeltaYArray = new LinkedList(); + + mCurrentX = mParams.x; + mCurrentY = mParams.y; + + mDraggableIconImage.setScaleX(PRESSED_FAB_SCALE); + mDraggableIconImage.setScaleY(PRESSED_FAB_SCALE); + + // Cancel any currently running animations + if (mAnimationTask != null) { + mAnimationTask.cancel(); + } + break; + case MotionEvent.ACTION_UP: + mIsAnimationLocked = false; + if (mAnimationTask != null) { + mAnimationTask.cancel(); + } + + if (!mDragged) { + // clicked so show theme list + final int mid = getScreenWidth() / 2; + mListSide = LIST_ON_LEFT_SIDE; + if (mCurrentPosX > mid) mListSide = LIST_ON_RIGHT_SIDE; + if (!mThemeListLayout.isAttachedToWindow()) showThemeList(); + } else { + // Animate the icon + mAnimationTask = new AnimationTask(); + mAnimationTask.run(); + } + + if (mIsInDeleteMode) { + close(true); + } else { + hideDeleteBox(); + mDraggableIconImage.setScaleX(1f); + mDraggableIconImage.setScaleY(1f); + } + break; + case MotionEvent.ACTION_MOVE: + mCurrentX = (int) (event.getRawX() - mDraggableIcon.getWidth() / 2); + mCurrentY = (int) (event.getRawY() - mDraggableIcon.getHeight()); + if (isDeleteMode()) { + mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_hover); + mIsInDeleteMode = true; + updateIconPosition(mCurrentX, mCurrentY); + } else if (mIsInDeleteMode){ + mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_normal); + mIsInDeleteMode = false; + } else { + if(!mIsAnimationLocked && mDragged) { + if (mAnimationTask != null) { + mAnimationTask.cancel(); + } + updateIconPosition(mCurrentX, mCurrentY); + } + } + + float deltaX = event.getRawX() - mPrevDragX; + float deltaY = event.getRawY() - mPrevDragY; + + mDeltaXArray.add(deltaX); + mDeltaYArray.add(deltaY); + + mPrevDragX = event.getRawX(); + mPrevDragY = event.getRawY(); + + deltaX = event.getRawX() - mOrigX; + deltaY = event.getRawY() - mOrigY; + mDragged = mDragged || Math.abs(deltaX) > DRAG_DELTA + || Math.abs(deltaY) > DRAG_DELTA; + if (mDragged) { + showDeleteBox(); + } + break; + } + + return true; + } + + @Override + public void onDestroy() { + super.onDestroy(); + mIsDestroyed = true; + if (mDraggableIcon != null) { + removeViewIfAttached(mDraggableIcon); + mDraggableIcon = null; + } + if (mDeleteView != null) { + removeViewIfAttached(mDeleteView); + mDeleteView = null; + } + if (mThemeListLayout != null) { + removeViewIfAttached(mThemeListLayout); + mThemeListLayout = null; + } + if (mAnimationTask != null) { + mAnimationTask.cancel(); + mAnimationTask = null; + } + if (mBroadcastReceiver != null) { + unregisterReceiver(mBroadcastReceiver); + mBroadcastReceiver = null; + } + if (mThemesObserver != null) { + getContentResolver().unregisterContentObserver(mThemesObserver); + mThemesObserver = null; + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + mThemeConfig = getThemeConfig(newConfig); + } + + @Override + public void onProgress(int progress) { + } + + @Override + public void onFinish(boolean isSuccess) { + ThemeManager tm = ThemeManager.getInstance(getContext()); + tm.removeClient(this); + mThemeListLayout.postDelayed(new Runnable() { + @Override + public void run() { + hideScrim(); + startFabScaleUpAnimation(); + } + }, THEME_CHANGE_DELAY); + } + + public void hideThemeList() { + hideThemeList(false, new Runnable() { + @Override + public void run() { + removeViewIfAttached(mThemeListLayout); + } + }); + } + + private ThemeConfig getThemeConfig(Configuration config) { + if (config != null && config.themeConfig != null) { + return config.themeConfig; + } + + return ThemeConfig.getBootTheme(getContentResolver()); + } + + private void removeViewIfAttached(View view) { + if (view.isAttachedToWindow()) { + mWindowManager.removeViewImmediate(view); + } + } + + private WindowManager.LayoutParams addView(View v, int x, int y) { + return addView(v, x, y, Gravity.TOP | Gravity.LEFT, + WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); + } + + private WindowManager.LayoutParams addView(View v, int x, int y, int gravity, + int width, int height) { + mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + WindowManager.LayoutParams params = new WindowManager.LayoutParams(width, height, + WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT); + + params.gravity = gravity; + params.x = x; + params.y = y; + + mWindowManager.addView(v, params); + + return params; + } + + private void updateIconPosition(int x, int y) { + mCurrentPosX = x; + + View v = mDraggableIconImage; + v.setTranslationX(0); + if (x < 0) { + v.setTranslationX(x); + x = 0; + } + + if (x > getScreenWidth() - FLOATING_WINDOW_ICON_SIZE) { + v.setTranslationX(x - getScreenWidth() + FLOATING_WINDOW_ICON_SIZE); + x = getScreenWidth() - FLOATING_WINDOW_ICON_SIZE; + } + + v.setTranslationY(0); + if (y < 0) { + v.setTranslationY(y); + y = 0; + } + + if (y > getScreenHeight() - FLOATING_WINDOW_ICON_SIZE) { + v.setTranslationY(y - getScreenHeight() + FLOATING_WINDOW_ICON_SIZE); + y = getScreenHeight() - FLOATING_WINDOW_ICON_SIZE; + } + mParams.x = x; + mParams.y = y; + + if (!mIsDestroyed) { + mWindowManager.updateViewLayout(mDraggableIcon, mParams); + } + } + + private boolean isDeleteMode() { + return isHoveringOverDeleteBox(mParams.y); + } + + private boolean isHoveringOverDeleteBox(int y) { + return y + mDraggableIconImage.getHeight() >= getScreenHeight() - DELETE_BOX_HEIGHT; + } + + private void showDeleteBox() { + if (!mDeleteBoxVisible) { + mDeleteBoxVisible = true; + mDeleteView.setVisibility(View.VISIBLE); + + mDeleteBoxView.setAlpha(0); + mDeleteBoxView.setTranslationY(CLOSE_ANIMATION_DISTANCE); + mDeleteBoxView.animate().alpha(1).translationYBy(-1 * CLOSE_ANIMATION_DISTANCE) + .setListener(null); + + mDeleteBoxView.getLayoutParams().width = getScreenWidth(); + } + } + + private void hideDeleteBox() { + if (mDeleteBoxVisible) { + mDeleteBoxVisible = false; + if (mDeleteView != null) { + mDeleteBoxView.animate().alpha(0) + .translationYBy(CLOSE_ANIMATION_DISTANCE) + .setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + if (mDeleteView != null) mDeleteView.setVisibility(View.GONE); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } + } + } + + private void animateToDeleteBoxCenter(final OnAnimationFinishedListener l) { + if (mIsAnimationLocked) { + return; + } + mIsInDeleteMode = true; + + if (mAnimationTask != null) { + mAnimationTask.cancel(); + } + + mAnimationTask = new AnimationTask(getScreenWidth() / 2 - mDraggableIcon.getWidth() / 2, + getScreenHeight() - DELETE_BOX_HEIGHT / 2 - mDraggableIcon.getHeight() / 2); + mAnimationTask.setDuration(MOVE_TO_DELETE_BOX_ANIMATION_DURATION); + mAnimationTask.setAnimationFinishedListener(l); + mAnimationTask.run(); + mDeleteBoxView.setBackgroundResource(R.drawable.btn_quicktheme_remove_hover); + } + + private void close(boolean animate) { + if (mIsBeingDestroyed) { + return; + } + mIsBeingDestroyed = true; + + if (animate) { + animateToDeleteBoxCenter(new OnAnimationFinishedListener() { + @Override + public void onAnimationFinished() { + hideDeleteBox(); + mDeleteBoxView.animate() + .scaleX(DELETE_BOX_ANIMATION_SCALE) + .scaleY(DELETE_BOX_ANIMATION_SCALE); + mDraggableIconImage.animate() + .scaleX(DELETE_BOX_ANIMATION_SCALE) + .scaleY(DELETE_BOX_ANIMATION_SCALE) + .translationY(CLOSE_ANIMATION_DISTANCE) + .setDuration(mDeleteBoxView.animate().getDuration()) + .setListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + stopSelf(); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } + }); + } else { + stopSelf(); + } + } + + private static interface OnAnimationFinishedListener { + public void onAnimationFinished(); + } + + private Context getContext() { + return this; + } + + private int getScreenWidth() { + return getResources().getDisplayMetrics().widthPixels; + } + + private int getScreenHeight() { + return getResources().getDisplayMetrics().heightPixels - getStatusBarHeight(); + } + + private int getStatusBarHeight() { + int result = 0; + int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + result = getResources().getDimensionPixelSize(resourceId); + } + + return result; + } + + private void loadThemes() { + String[] columns = {ThemesColumns._ID, ThemesColumns.TITLE, ThemesColumns.PKG_NAME}; + String selection = ThemesColumns.MODIFIES_OVERLAYS + "=? AND " + + ThemesColumns.INSTALL_STATE + "=?"; + String[] selectionArgs = {"1", "" + ThemesColumns.InstallState.INSTALLED}; + String sortOrder = ThemesColumns.TITLE + " ASC"; + Cursor c = getContentResolver().query(ThemesColumns.CONTENT_URI, columns, selection, + selectionArgs, sortOrder); + if (c != null) { + if (mAdapter == null) { + mAdapter = new ThemesAdapter(this, c); + mThemeList.setAdapter(mAdapter); + mThemeList.setOnItemClickListener(mThemeClickedListener); + } else { + String pkgName = (String) mAdapter.getItem(0); + mAdapter.populateThemes(c); + mAdapter.setCurrentTheme(pkgName); + } + } + } + + private ContentObserver mThemesObserver = new ContentObserver(new Handler()) { + @Override + public void onChange(boolean selfChange) { + onChange(selfChange, null); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + loadThemes(); + } + }; + + private ViewTreeObserver.OnWindowAttachListener mWindowAttachListener = + new ViewTreeObserver.OnWindowAttachListener() { + @Override + public void onWindowAttached() { + // Remove the this OnWindowAttachListener now that we are done with it. + mDraggableIconImage.getViewTreeObserver().removeOnWindowAttachListener(this); + + final float fabWidth = getResources().getDimension(R.dimen.floating_window_icon); + mDraggableIconImage.setAlpha(0); + mDraggableIconImage.setX(-fabWidth); + mDraggableIconImage.animate() + .alpha(1f) + .xBy(fabWidth) + .setDuration(ANIMATION_DURATION) + .start(); + } + + @Override + public void onWindowDetached() { + } + }; + + private void showThemeList() { + if (mListLayoutParams == null) { + mListLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_PHONE, + WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | + WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | + WindowManager.LayoutParams.FLAG_SPLIT_TOUCH, + PixelFormat.TRANSLUCENT); + } + mListLayoutParams.gravity = Gravity.TOP | + (mListSide == LIST_ON_LEFT_SIDE ? Gravity.LEFT : Gravity.RIGHT); + mWindowManager.addView(mThemeListLayout, mListLayoutParams); + + setThemeListPosition(); + startFabScaleDownAnimation(); + + mAdapter.setCurrentTheme( + mThemeConfig.getOverlayPkgNameForApp(Utils.getTopTaskPackageName(this))); + mThemeListLayout.circularReveal(mParams.x + mDraggableIconImage.getWidth() / 2, + mParams.y + mDraggableIconImage.getHeight() / 2, ANIMATION_DURATION); + } + + private void hideThemeList(boolean showScrim, final Runnable endAction) { + if (showScrim) { + showScrim(); + } else { + startFabScaleUpAnimation(); + } + mThemeListLayout.circularHide(mParams.x + mDraggableIconImage.getWidth() / 2, + mParams.y + mDraggableIconImage.getHeight() / 2, ANIMATION_DURATION); + if (endAction != null) { + mDraggableIcon.postDelayed(endAction, ANIMATION_DURATION); + } + } + + private void showScrim() { + ValueAnimator animator = ValueAnimator.ofArgb(SCRIM_COLOR_TRANSPARENT, + SCRIM_COLOR_OPAQUE); + mThemeListLayout.setEnabled(false); + animator.setDuration(ANIMATION_DURATION) + .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + Integer value = (Integer) animation.getAnimatedValue(); + mThemeListLayout.setBackgroundColor(value.intValue()); + } + }); + animator.start(); + mThemeApplyingView.animate() + .alpha(1f) + .setDuration(ANIMATION_DURATION); + } + + private void hideScrim() { + ValueAnimator animator = ValueAnimator.ofArgb(SCRIM_COLOR_OPAQUE, SCRIM_COLOR_TRANSPARENT); + animator.setDuration(ANIMATION_DURATION) + .addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + Integer value = (Integer) animation.getAnimatedValue(); + mThemeListLayout.setBackgroundColor(value.intValue()); + } + }); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } + + @Override + public void onAnimationEnd(Animator animation) { + removeViewIfAttached(mThemeListLayout); + } + + @Override + public void onAnimationCancel(Animator animation) { + } + + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + animator.start(); + mThemeApplyingView.animate() + .alpha(0f) + .setDuration(ANIMATION_DURATION); + mDraggableIcon.setVisibility(View.VISIBLE); + mDraggableIconImage.animate() + .alpha(1f) + .setDuration(ANIMATION_DURATION); + } + + private void setThemeListPosition() { + int thirdHeight = getScreenHeight() / 3; + // use the center of the fab to decide where to place the list + int fabLocationY = mParams.y + mDraggableIconImage.getHeight() / 2; + int listHeight = mThemeList.getMeasuredHeight(); + if (listHeight <= 0) { + // not measured yet so let's force that + int width = getResources().getDimensionPixelSize(R.dimen.theme_list_width); + int height = getResources().getDimensionPixelSize(R.dimen.theme_list_max_height); + mThemeList.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST)); + listHeight = mThemeList.getMeasuredHeight(); + } + + // If we're in the top 1/3 of the screen position the top of the list with the top + // of the fab. Second 3rd will position the list so that it is vertically centered + // with the fab center. Bottom 3rd will position the bottom of the list with the + // bottom of the fab. + if (fabLocationY < thirdHeight) { + mListParams.topMargin = mParams.y + mDraggableIconImage.getHeight() / 2; + } else if (fabLocationY < thirdHeight * 2) { + mListParams.topMargin = fabLocationY - listHeight / 2; + } else { + mListParams.topMargin = mParams.y + mDraggableIconImage.getHeight() / 2 - listHeight; + } + mListParams.gravity = Gravity.TOP | + (mListSide == LIST_ON_LEFT_SIDE ? Gravity.LEFT : Gravity.RIGHT); + mThemeList.setLayoutParams(mListParams); + } + + private void startFabScaleDownAnimation() { + final int iconWidth = mDraggableIconImage.getWidth(); + final float translateX = (iconWidth - (float) iconWidth * FAB_ANIMATION_SCALE_FACTOR) / 2 * + (mListSide == LIST_ON_LEFT_SIDE ? -1 : 1); + + mDraggableIconImage.animate() + .scaleX(FAB_ANIMATION_SCALE_FACTOR) + .scaleY(FAB_ANIMATION_SCALE_FACTOR) + .translationXBy(translateX) + .setDuration(FAB_SCALE_ANIMATION_DURATION); + } + + private void startFabScaleUpAnimation() { + final float iconWidth = mDraggableIconImage.getWidth(); + final float translateX = (iconWidth - (float) iconWidth * FAB_ANIMATION_SCALE_FACTOR) / 2 * + (mListSide == LIST_ON_LEFT_SIDE ? 1 : -1); + + mDraggableIconImage.animate() + .scaleX(1f) + .scaleY(1f) + .translationXBy(translateX) + .setDuration(FAB_SCALE_ANIMATION_DURATION); + } + + private AdapterView.OnItemClickListener mThemeClickedListener = + new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + final String themePkgName = (String) view.getTag(R.id.tag_key_name); + final String appPkgName = Utils.getTopTaskPackageName(getContext()); + if (!TextUtils.isEmpty(appPkgName) && !TextUtils.isEmpty(themePkgName)) { + if (!Utils.themeHasOverlayForApp(getContext(), appPkgName, themePkgName)) { + Toast.makeText(getContext(), R.string.per_app_theme_app_not_overlaid_warning, + Toast.LENGTH_LONG).show(); + } + hideThemeList(true, new Runnable() { + @Override + public void run() { + ThemeManager tm = ThemeManager.getInstance(getContext()); + ThemeChangeRequest.Builder builder = new ThemeChangeRequest.Builder(); + builder.setAppOverlay(appPkgName, themePkgName); + try { + tm.addClient(PerAppThemingWindow.this); + } catch (IllegalArgumentException e) { + /* ignore since this means we already have a listener added */ + } + tm.requestThemeChange(builder.build(), false); + } + }); + } else { + hideThemeList(); + } + } + }; + + private float calculateVelocityX() { + int depreciation = mDeltaXArray.size() + 1; + float sum = 0; + for (Float f : mDeltaXArray) { + depreciation--; + if (depreciation > MAX_DEPRECIATION){ + continue; + } + + sum += f / depreciation; + } + + return sum; + } + + private float calculateVelocityY() { + int depreciation = mDeltaYArray.size() + 1; + float sum = 0; + for (Float f : mDeltaYArray) { + depreciation--; + if (depreciation > 5) { + continue; + } + + sum += f / depreciation; + } + + return sum; + } + + // Timer for animation/automatic movement of the tray + private class AnimationTask { + // Ultimate destination coordinates toward which the view will move + int mDestX; + int mDestY; + long mDuration = 350; + long mStartTime; + float mTension = 1.4f; + Interpolator mInterpolator = new OvershootInterpolator(mTension); + long mSteps; + long mCurrentStep; + int mDistX; + int mOrigX; + int mDistY; + int mOrigY; + Handler mAnimationHandler = new Handler(); + OnAnimationFinishedListener mAnimationFinishedListener; + + public AnimationTask(int x, int y) { + setup(x, y); + } + + public AnimationTask() { + setup(calculateX(), calculateY()); + + float velocityX = calculateVelocityX(); + float velocityY = calculateVelocityY(); + mTension += Math.sqrt(velocityX * velocityX + velocityY * velocityY) / 200; + mInterpolator = new OvershootInterpolator(mTension); + } + + private void setup(int x, int y) { + if (mIsAnimationLocked) { + throw new RuntimeException("Returning to user's finger. Avoid animations while " + + "mIsAnimationLocked flag is set."); + } + + mDestX = x; + mDestY = y; + + mSteps = (int) (((float) mDuration) / 1000 * ANIMATION_FRAME_RATE); + mCurrentStep = 1; + mDistX = mParams.x - mDestX; + mOrigX = mParams.x; + mDistY = mParams.y - mDestY; + mOrigY = mParams.y; + } + + public long getDuration() { + return mDuration; + } + + public void setDuration(long duration) { + mDuration = duration; + setup(mDestX, mDestY); + } + + public OnAnimationFinishedListener getAnimationFinishedListener() { + return mAnimationFinishedListener; + } + + public void setAnimationFinishedListener(OnAnimationFinishedListener l) { + mAnimationFinishedListener = l; + } + + public Interpolator getInterpolator() { + return mInterpolator; + } + + public void setInterpolator(Interpolator interpolator) { + mInterpolator = interpolator; + } + + private int calculateX() { + float velocityX = calculateVelocityX(); + int screenWidth = getScreenWidth(); + int destX = (mParams.x + mDraggableIcon.getWidth() / 2 > screenWidth / 2) + ? screenWidth - mDraggableIcon.getWidth() - MARGIN_HORIZONTAL + : 0 + MARGIN_HORIZONTAL; + + if (Math.abs(velocityX) > 50) { + destX = (velocityX > 0) ? screenWidth - mDraggableIcon.getWidth() + - MARGIN_HORIZONTAL : 0 + MARGIN_HORIZONTAL; + } + + return destX; + } + + private int calculateY() { + float velocityY = calculateVelocityY(); + mInterpolator = new OvershootInterpolator(mTension); + int screenHeight = getScreenHeight(); + int destY = mParams.y + (int) (velocityY * 3); + if (destY <= 0) { + destY = MARGIN_VERTICAL; + } + if (destY >= screenHeight - mDraggableIcon.getHeight()) { + destY = screenHeight - mDraggableIcon.getHeight() - MARGIN_VERTICAL; + } + + return destY; + } + + public void run() { + mStartTime = System.currentTimeMillis(); + for (mCurrentStep = 1; mCurrentStep <= mSteps; mCurrentStep++) { + long delay = mCurrentStep * mDuration / mSteps; + final float currentStep = mCurrentStep; + mAnimationHandler.postDelayed(new Runnable() { + @Override + public void run() { + // Update coordinates of the view + float percent = mInterpolator.getInterpolation(currentStep / mSteps); + updateIconPosition(mOrigX - (int) (percent * mDistX), mOrigY + - (int) (percent * mDistY)); + + // Notify the animation has ended + if (currentStep >= mSteps) { + if (mAnimationFinishedListener != null) mAnimationFinishedListener + .onAnimationFinished(); + } + } + }, delay); + } + } + + public void cancel() { + mAnimationHandler.removeCallbacksAndMessages(null); + mAnimationTask = null; + } + } + + /** + * We're extending BaseAdapter rather than CursorAdapter so that we can quickly re-order + * the list without needing to requery the provider. We're only storing the package name + * and theme title so there is minimum memory impact on doing this. + */ + class ThemesAdapter extends BaseAdapter { + private static final float HALF_OPACITY = 0.5f; + private static final float FULL_OPACITY = 1.0f; + + private ArrayList mThemes; + private LayoutInflater mInflater; + + public ThemesAdapter(Context context, Cursor cursor) { + mInflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE); + mThemes = new ArrayList(cursor.getCount()); + populateThemes(cursor); + cursor.close(); + } + + @Override + public int getCount() { + return mThemes.size(); + } + + @Override + public Object getItem(int position) { + return mThemes.get(position).pkgName; + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + convertView = mInflater.inflate(R.layout.per_app_theme_list_item, parent, false); + Holder holder = new Holder(); + holder.title = (TextView) convertView.findViewById(R.id.theme_title); + holder.indicator = (TextView) convertView.findViewById(R.id.selected_indicator); + convertView.setTag(R.id.tag_key_holder, holder); + } + ThemeInfo themeInfo = mThemes.get(position); + Holder holder = (Holder) convertView.getTag(R.id.tag_key_holder); + holder.title.setText(themeInfo.title); + if (position == 0) { + holder.title.setAlpha(HALF_OPACITY); + holder.indicator.setVisibility(View.VISIBLE); + convertView.setEnabled(false); + } else { + holder.title.setAlpha(FULL_OPACITY); + holder.indicator.setVisibility(View.INVISIBLE); + convertView.setEnabled(true); + } + convertView.setTag(R.id.tag_key_name, themeInfo.pkgName); + return convertView; + } + + @Override + public boolean isEnabled(int position) { + return position != 0; + } + + public void setCurrentTheme(String pkgName) { + ThemeInfo info = null; + for (ThemeInfo ti : mThemes) { + if (ti.pkgName.equals(pkgName)) { + info = ti; + break; + } + } + if (info != null) { + Collections.sort(mThemes); + mThemes.remove(info); + mThemes.add(0, info); + notifyDataSetChanged(); + } + } + + private void populateThemes(Cursor cursor) { + mThemes.clear(); + while(cursor.moveToNext()) { + ThemeInfo info = new ThemeInfo( + cursor.getString(cursor.getColumnIndex(ThemesColumns.PKG_NAME)), + cursor.getString(cursor.getColumnIndex(ThemesColumns.TITLE))); + mThemes.add(info); + } + } + + private class Holder { + TextView title; + TextView indicator; + } + + private class ThemeInfo implements Comparable { + String pkgName; + String title; + + public ThemeInfo(String pkgName, String title) { + this.pkgName = pkgName; + this.title = title; + } + + @Override + public int compareTo(Object another) { + return this.title.compareTo(((ThemeInfo)another).title); + } + } + } +} diff --git a/src/org/cyanogenmod/theme/util/AudioUtils.java b/src/org/cyanogenmod/theme/util/AudioUtils.java new file mode 100644 index 0000000..c4ad68e --- /dev/null +++ b/src/org/cyanogenmod/theme/util/AudioUtils.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.content.res.ThemeConfig; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.util.Log; + +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.File; +import java.io.IOException; + +public class AudioUtils { + private static final String TAG = AudioUtils.class.getSimpleName(); + + public static void loadThemeAudible(Context context, int type, String pkgName, MediaPlayer mp) + throws PackageManager.NameNotFoundException { + if (ThemeConfig.SYSTEM_DEFAULT.equals(pkgName)) { + loadSystemAudible(type, mp); + return; + } + PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); + Context themeCtx = context.createPackageContext(pkgName, 0); + AssetManager assetManager = themeCtx.getAssets(); + String assetPath; + switch (type) { + case RingtoneManager.TYPE_ALARM: + assetPath = "alarms"; + break; + case RingtoneManager.TYPE_NOTIFICATION: + assetPath = "notifications"; + break; + case RingtoneManager.TYPE_RINGTONE: + assetPath = "ringtones"; + break; + default: + assetPath = null; + break; + } + if (assetPath != null) { + try { + String[] assetList = assetManager.list(assetPath); + if (assetList != null && assetList.length > 0) { + AssetFileDescriptor afd = assetManager.openFd(assetPath + + File.separator + assetList[0]); + if (mp != null) { + mp.reset(); + mp.setDataSource(afd.getFileDescriptor(), + afd.getStartOffset(), afd.getLength()); + mp.prepare(); + } + } + } catch (IOException e) { + Log.e(TAG, "Unable to load sound for " + pkgName, e); + } + } + } + + public static void loadSystemAudible(int type, MediaPlayer mp) { + final String audiblePath = ThemeUtils.getDefaultAudiblePath(type); + if (audiblePath != null && (new File(audiblePath)).exists()) { + try { + mp.reset(); + mp.setDataSource(audiblePath); + mp.prepare(); + } catch (IOException e) { + Log.e(TAG, "Unable to load system sound " + audiblePath, e); + } + } + } + + public static Uri loadDefaultAudible(Context context, int type, MediaPlayer mp) + throws IOException { + Uri ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); + if (ringtoneUri != null) { + mp.reset(); + mp.setDataSource(context, ringtoneUri); + mp.prepare(); + } + + return ringtoneUri; + } +} diff --git a/src/org/cyanogenmod/theme/util/BootAnimationHelper.java b/src/org/cyanogenmod/theme/util/BootAnimationHelper.java new file mode 100644 index 0000000..1bd4ee7 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/BootAnimationHelper.java @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.res.ThemeConfig; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.AsyncTask; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.widget.ImageView; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +public class BootAnimationHelper { + private static final String TAG = BootAnimationHelper.class.getSimpleName(); + private static final int MAX_REPEAT_COUNT = 3; + + public static final String THEME_INTERNAL_BOOT_ANI_PATH = + "assets/bootanimation/bootanimation.zip"; + public static final String SYSTEM_BOOT_ANI_PATH = "/system/media/bootanimation.zip"; + public static final String CACHED_SUFFIX = "_bootanimation.zip"; + + public static final int NUM_FIRST_LINE_PARAMETERS = 3; + public static final int NUM_PART_LINE_PARAMETERS = 4; + + public static class AnimationPart { + /** + * Number of times to play this part + */ + public int playCount; + /** + * If non-zero, pause for the given # of seconds before moving on to next part. + */ + public int pause; + /** + * The name of this part + */ + public String partName; + /** + * Time each frame is displayed + */ + public int frameRateMillis; + /** + * List of file names for the given frames in this part + */ + public List frames; + /** + * width of the animation + */ + public int width; + /** + * height of the animation + */ + public int height; + + public AnimationPart(int playCount, int pause, String partName, int frameRateMillis, + int width, int height) { + this.playCount = playCount == 0 ? MAX_REPEAT_COUNT : playCount; + this.pause = pause; + this.partName = partName; + this.frameRateMillis = frameRateMillis; + this.width = width; + this.height = height; + frames = new ArrayList(); + } + + public void addFrame(String frame) { + frames.add(frame); + } + } + + /** + * Gather up all the details for the given boot animation + * @param zip The bootanimation.zip + * @return A list of AnimationPart if successful, null if not. + * @throws IOException + */ + public static List parseAnimation(ZipFile zip) + throws IOException, BootAnimationException { + if (zip == null) { + // To make tracking down boot animation problems we'll throw a BootAnimationException + // instead of an IllegalArgumentException. + throw new BootAnimationException("Boot animation ZipFile cannot be null"); + } + List animationParts = null; + + ZipEntry ze = zip.getEntry("desc.txt"); + if (ze != null) { + animationParts = parseDescription(zip.getInputStream(ze)); + } else { + throw new BootAnimationException("Missing desc.txt in root of bootanimation.zip"); + } + + if (animationParts == null) { + // We really should not end up here but in case we do here's an exception for ya! + throw new BootAnimationException("Unable to load boot animation."); + } + + Iterator iterator = animationParts.iterator(); + while(iterator.hasNext()) { + AnimationPart a = iterator.next(); + for (Enumeration e = zip.entries();e.hasMoreElements();) { + ze = e.nextElement(); + if (!ze.isDirectory() && ze.getName().contains(a.partName)) { + a.addFrame(ze.getName()); + } + } + if (a.frames.size() > 0) { + Collections.sort(a.frames); + } else { + // This boot animation may be salvageable if there are still some other parts + // that are good. We'll remove this part and if there are no parts left by + // the time we have iterated over all the parts then we can throw an exception. + Log.w(TAG, String.format("No frames in part %s, removing from animation", + a.partName)); + iterator.remove(); + } + } + if (animationParts.size() == 0) { + throw new BootAnimationException("Boot animation must have at least one part."); + } + + return animationParts; + } + + /** + * Parses the desc.txt of the boot animation + * @param in InputStream to the desc.txt + * @return A list of the parts as given in desc.txt + * @throws IOException + */ + private static List parseDescription(InputStream in) + throws IOException, BootAnimationException { + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + + // read in suggested width, height, and frame rate from first line + String line = reader.readLine(); + String[] details = line.split(" "); + if (details.length != NUM_FIRST_LINE_PARAMETERS) { + throw new BootAnimationException(String.format( + "Invalid # of parameters on first line of desc.txt; exptected %d, read %d " + + "(\"%s\")", + NUM_FIRST_LINE_PARAMETERS, details.length, line)); + } + + // The items should be in the following order: width, height, frame rate + final int width = Integer.parseInt(details[0]); + final int height = Integer.parseInt(details[1]); + final int frameRateMillis = 1000 / Integer.parseInt(details[2]); + + List animationParts = new ArrayList(); + while ((line = reader.readLine()) != null) { + // trim off any leading and trailing spaces + line = line.trim(); + // if the line is empty continue on to the next + if (TextUtils.isEmpty(line)) continue; + + String[] info = line. split(" "); + if (info.length != NUM_PART_LINE_PARAMETERS) { + Log.w(TAG, String.format( + "Invalid # of part parameters; exptected %d, read %d (\"%s\")", + NUM_PART_LINE_PARAMETERS, info.length, line)); + // let's continue in case there are parts that are valid + continue; + } + if (!info[0].equals("p") && !info[0].equals("c")) { + Log.w(TAG, String.format( + "Unknown part type; expected 'p' or 'c', read %s (\"%s\")", info[0], line)); + + // let's continue in case there are parts that are valid + continue; + } + int playCount = Integer.parseInt(info[1]); + int pause = Integer.parseInt(info[2]); + String name = info[3]; + AnimationPart ap = new AnimationPart(playCount, pause, name, frameRateMillis, + width, height); + animationParts.add(ap); + } + + return animationParts; + } + + public static String getPreviewFrameEntryName(InputStream is) throws IOException { + ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is + : new ZipInputStream(new BufferedInputStream(is)); + ZipEntry ze; + // First thing to do is iterate over all the entries and the zip and store them + // for building the animations afterwards + String previewName = null; + while ((ze = zis.getNextEntry()) != null) { + final String entryName = ze.getName(); + if (entryName.contains("/") + && (entryName.endsWith(".png") || entryName.endsWith(".jpg"))) { + previewName = entryName; + } + } + + return previewName; + } + + public static Bitmap loadPreviewFrame(Context context, InputStream is, String previewName) + throws IOException { + ZipInputStream zis = (is instanceof ZipInputStream) ? (ZipInputStream) is + : new ZipInputStream(new BufferedInputStream(is)); + ZipEntry ze; + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inSampleSize = am.isLowRamDevice() ? 4 : 2; + opts.inPreferredConfig = Bitmap.Config.RGB_565; + // First thing to do is iterate over all the entries and the zip and store them + // for building the animations afterwards + Bitmap preview = null; + while ((ze = zis.getNextEntry()) != null && preview == null) { + final String entryName = ze.getName(); + if (entryName.equals(previewName)) { + preview = BitmapFactory.decodeStream(zis, null, opts); + } + } + zis.close(); + + return preview; + } + + public static void clearBootAnimationCache(Context context) { + File cache = context.getCacheDir(); + if (cache.exists()) { + for(File f : cache.listFiles()) { + // volley stores stuff in cache so don't delete the volley directory + if(!f.isDirectory() && f.getName().endsWith(CACHED_SUFFIX)) f.delete(); + } + } + } + + public static class LoadBootAnimationImage extends AsyncTask { + private ImageView imv; + private String path; + private Context context; + + public LoadBootAnimationImage(ImageView imv, Context context, String path) { + this.imv = imv; + this.context = context; + this.path = path; + } + + @Override + protected Bitmap doInBackground(Object... params) { + Bitmap bitmap = null; + String previewName = null; + // this is ugly, ugly, ugly. Did I mention this is ugly? + try { + if (ThemeConfig.SYSTEM_DEFAULT.equals(path)) { + previewName = getPreviewFrameEntryName( + new FileInputStream(SYSTEM_BOOT_ANI_PATH)); + bitmap = loadPreviewFrame( + context, new FileInputStream(SYSTEM_BOOT_ANI_PATH), previewName); + } else { + final Context themeCtx = context.createPackageContext(path, 0); + previewName = getPreviewFrameEntryName( + themeCtx.getAssets().open("bootanimation/bootanimation.zip")); + bitmap = loadPreviewFrame(context, + themeCtx.getAssets().open("bootanimation/bootanimation.zip"), + previewName); + } + } catch (Exception e) { + // don't care since a null bitmap will be returned + e.printStackTrace(); + } + return bitmap; + } + + @Override + protected void onPostExecute(Bitmap result) { + if (result != null && imv != null) { + imv.setVisibility(View.VISIBLE); + imv.setImageBitmap(result); + } + } + } + + public static class BootAnimationException extends Exception { + public BootAnimationException(String detailMessage) { + super(detailMessage); + } + } +} diff --git a/src/org/cyanogenmod/theme/util/CursorLoaderHelper.java b/src/org/cyanogenmod/theme/util/CursorLoaderHelper.java new file mode 100644 index 0000000..7fcd786 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/CursorLoaderHelper.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; + +import cyanogenmod.app.ThemeVersion; +import cyanogenmod.providers.ThemesContract; +import cyanogenmod.providers.ThemesContract.PreviewColumns; +import cyanogenmod.providers.ThemesContract.ThemesColumns; + +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ALARMS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_RINGTONES; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_ICONS; +import static cyanogenmod.providers.ThemesContract.ThemesColumns.MODIFIES_FONTS; + +public class CursorLoaderHelper { + + public static final int LOADER_ID_INVALID = -1; + public static final int LOADER_ID_ALL = 0; + public static final int LOADER_ID_STATUS_BAR = 1; + public static final int LOADER_ID_FONT = 2; + public static final int LOADER_ID_ICONS = 3; + public static final int LOADER_ID_WALLPAPER = 4; + public static final int LOADER_ID_NAVIGATION_BAR = 5; + public static final int LOADER_ID_LOCKSCREEN = 6; + public static final int LOADER_ID_STYLE = 7; + public static final int LOADER_ID_BOOT_ANIMATION = 8; + public static final int LOADER_ID_RINGTONE = 9; + public static final int LOADER_ID_NOTIFICATION = 10; + public static final int LOADER_ID_ALARM = 11; + public static final int LOADER_ID_LIVE_LOCK_SCREEN = 12; + public static final int LOADER_ID_INSTALLED_THEMES = 1000; + public static final int LOADER_ID_APPLIED = 1001; + + private static final long DEFAULT_COMPONENT_ID = 0; + + private static int mThemeVersion = ThemeVersion.getVersion(); + + public static Loader chooserActivityCursorLoader(Context context, int id, + String appliedBaseTheme) { + String selection = null; + String selectionArgs[] = null; + String sortOrder = null; + String[] projection = null; + Uri contentUri = null; + + switch (id) { + case LOADER_ID_INSTALLED_THEMES: + selection = ThemesColumns.PRESENT_AS_THEME + "=? AND " + + ThemesColumns.INSTALL_STATE + "=?"; + selectionArgs = new String[] { "1", "" + ThemesColumns.InstallState.INSTALLED}; + // sort in ascending order but make sure the "default" theme is always first + sortOrder = "(" + ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, " + + "(" + ThemesColumns.PKG_NAME + "='" + appliedBaseTheme + "') DESC, " + + ThemesColumns.INSTALL_TIME + " DESC"; + contentUri = ThemesColumns.CONTENT_URI; + projection = new String[] {ThemesColumns.PKG_NAME, ThemesColumns.TITLE, + ThemesColumns.AUTHOR}; + break; + case LOADER_ID_APPLIED: + //TODO: Mix n match query should only be done once + contentUri = ThemesContract.MixnMatchColumns.CONTENT_URI; + selection = null; + selectionArgs = null; + break; + } + + return new CursorLoader(context, contentUri, projection, selection, + selectionArgs, sortOrder); + } + + public static Loader componentSelectorCursorLoader(Context context, int id) { + Uri uri = PreviewColumns.CONTENT_URI; + String selection; + String[] selectionArgs = { "1" }; + String[] projection = { ThemesColumns.TITLE, ThemesColumns.PKG_NAME }; + switch(id) { + case LOADER_ID_STATUS_BAR: + selection = MODIFIES_STATUS_BAR + "=?"; + projection = new String[] { + PreviewColumns.STATUSBAR_WIFI_ICON, + PreviewColumns.STATUSBAR_SIGNAL_ICON, + PreviewColumns.STATUSBAR_BLUETOOTH_ICON, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.STATUSBAR_BATTERY_CIRCLE, + PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, + PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + break; + case LOADER_ID_NAVIGATION_BAR: + selection = MODIFIES_NAVIGATION_BAR + "=?"; + projection = new String[] { + PreviewColumns.NAVBAR_BACK_BUTTON, + PreviewColumns.STATUSBAR_BACKGROUND, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME, + }; + break; + case LOADER_ID_FONT: + // fonts don't have generated previews so use the ThemesColumns.CONTENT_URI + uri = ThemesColumns.CONTENT_URI; + selection = MODIFIES_FONTS + "=?"; + break; + case LOADER_ID_ICONS: + selection = MODIFIES_ICONS + "=?"; + projection = new String[] { + PreviewColumns.ICON_PREVIEW_1, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + break; + case LOADER_ID_STYLE: + selection = MODIFIES_OVERLAYS + "=?"; + projection = new String[] { + PreviewColumns.STYLE_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + break; + case LOADER_ID_WALLPAPER: + selection = MODIFIES_LAUNCHER + "=?"; + if (mThemeVersion >= 3) { + uri = PreviewColumns.COMPONENTS_URI; + projection = new String[]{ + PreviewColumns.WALLPAPER_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME, + PreviewColumns.COMPONENT_ID + }; + } else { + projection = new String[]{ + PreviewColumns.WALLPAPER_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + } + break; + case LOADER_ID_BOOT_ANIMATION: + selection = MODIFIES_BOOT_ANIM + "=?"; + projection = new String[] { + PreviewColumns.BOOTANIMATION_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME + }; + break; + case LOADER_ID_RINGTONE: + selection = MODIFIES_RINGTONES + "=?"; + break; + case LOADER_ID_NOTIFICATION: + selection = MODIFIES_NOTIFICATIONS + "=?"; + break; + case LOADER_ID_ALARM: + selection = MODIFIES_ALARMS + "=?"; + break; + case LOADER_ID_LOCKSCREEN: + selection = MODIFIES_LOCKSCREEN + "=?"; + selectionArgs = new String[] { "1" }; + if (mThemeVersion >= 3) { + projection = new String[]{ + PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, + PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + PreviewColumns.COMPONENT_ID + }; + } else { + projection = new String[]{ + PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, + PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + ThemesColumns.PKG_NAME + }; + } + break; + case LOADER_ID_LIVE_LOCK_SCREEN: + selection = MODIFIES_LIVE_LOCK_SCREEN + "=?"; + selectionArgs = new String[] { "1" }; + if (mThemeVersion >= 3) { + projection = new String[]{ + PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.PKG_NAME, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + PreviewColumns.COMPONENT_ID + }; + } else { + projection = new String[]{ + PreviewColumns.LOCK_WALLPAPER_THUMBNAIL, + PreviewColumns.LIVE_LOCK_SCREEN_THUMBNAIL, + ThemesColumns.TITLE, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + ThemesColumns.PKG_NAME + }; + } + break; + default: + return null; + } + // sort in ascending order but make sure the "default" theme is always first + String sortOrder = "(" + ThemesContract.ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, " + + ThemesContract.ThemesColumns.TITLE + " ASC"; + return new CursorLoader(context, uri, projection, selection, selectionArgs, sortOrder); + } + + public static Loader myThemeFragmentCursorLoader(Context context, int id) { + Uri uri; + String[] projection; + projection = new String[]{ + PreviewColumns.WALLPAPER_PREVIEW, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.STATUSBAR_WIFI_ICON, + PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, + PreviewColumns.STATUSBAR_BLUETOOTH_ICON, + PreviewColumns.STATUSBAR_SIGNAL_ICON, + PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, + PreviewColumns.STATUSBAR_BATTERY_CIRCLE, + PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, + PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, + PreviewColumns.NAVBAR_BACK_BUTTON, + PreviewColumns.NAVBAR_HOME_BUTTON, + PreviewColumns.NAVBAR_RECENT_BUTTON, + PreviewColumns.ICON_PREVIEW_1, + PreviewColumns.ICON_PREVIEW_2, + PreviewColumns.ICON_PREVIEW_3, + PreviewColumns.LOCK_WALLPAPER_PREVIEW, + PreviewColumns.STYLE_PREVIEW, + PreviewColumns.NAVBAR_BACKGROUND, + PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW + }; + uri = PreviewColumns.APPLIED_URI; + return new CursorLoader(context, uri, projection, null, null, null); + } + + public static Loader themeFragmentCursorLoader(Context context, int id, String pkgName, + long componentId) { + Uri uri = PreviewColumns.CONTENT_URI; + String selection = ThemesContract.ThemesColumns.PKG_NAME + "= ?"; + String[] selectionArgs = new String[] { pkgName }; + String[] projection = null; + switch (id) { + case LOADER_ID_ALL: + if (mThemeVersion >= 3) { + // Load all default component previews (component_id == 0) + selection += " AND " + PreviewColumns.COMPONENT_ID + "=?"; + selectionArgs = new String[] { pkgName, String.valueOf(DEFAULT_COMPONENT_ID) }; + } else { + // SQL query will fail if we ask for PreviewColumns.COMPONENT_ID, don't add it. + selectionArgs = new String[]{pkgName}; + } + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + ThemesColumns.AUTHOR, + ThemesColumns.WALLPAPER_URI, + ThemesColumns.HOMESCREEN_URI, + ThemesColumns.TARGET_API, + // Theme abilities + ThemesColumns.MODIFIES_LAUNCHER, + ThemesColumns.MODIFIES_LOCKSCREEN, + ThemesColumns.MODIFIES_ALARMS, + ThemesColumns.MODIFIES_BOOT_ANIM, + ThemesColumns.MODIFIES_FONTS, + ThemesColumns.MODIFIES_ICONS, + ThemesColumns.MODIFIES_NAVIGATION_BAR, + ThemesColumns.MODIFIES_OVERLAYS, + ThemesColumns.MODIFIES_RINGTONES, + ThemesColumns.MODIFIES_STATUS_BAR, + ThemesColumns.MODIFIES_NOTIFICATIONS, + //Previews + PreviewColumns.WALLPAPER_PREVIEW, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.STATUSBAR_WIFI_ICON, + PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, + PreviewColumns.STATUSBAR_BLUETOOTH_ICON, + PreviewColumns.STATUSBAR_SIGNAL_ICON, + PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, + PreviewColumns.STATUSBAR_BATTERY_CIRCLE, + PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, + PreviewColumns.STATUSBAR_BATTERY_PORTRAIT, + PreviewColumns.NAVBAR_BACK_BUTTON, + PreviewColumns.NAVBAR_HOME_BUTTON, + PreviewColumns.NAVBAR_RECENT_BUTTON, + PreviewColumns.ICON_PREVIEW_1, + PreviewColumns.ICON_PREVIEW_2, + PreviewColumns.ICON_PREVIEW_3, + PreviewColumns.LOCK_WALLPAPER_PREVIEW, + PreviewColumns.STYLE_PREVIEW, + PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW + }; + break; + case LOADER_ID_STATUS_BAR: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.STATUSBAR_WIFI_ICON, + PreviewColumns.STATUSBAR_WIFI_COMBO_MARGIN_END, + PreviewColumns.STATUSBAR_BLUETOOTH_ICON, + PreviewColumns.STATUSBAR_SIGNAL_ICON, + PreviewColumns.STATUSBAR_CLOCK_TEXT_COLOR, + PreviewColumns.STATUSBAR_BATTERY_CIRCLE, + PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE, + PreviewColumns.STATUSBAR_BATTERY_PORTRAIT + }; + break; + case LOADER_ID_FONT: + uri = ThemesColumns.CONTENT_URI; + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE + }; + break; + case LOADER_ID_ICONS: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.ICON_PREVIEW_1, + PreviewColumns.ICON_PREVIEW_2, + PreviewColumns.ICON_PREVIEW_3, + }; + break; + case LOADER_ID_WALLPAPER: + if (mThemeVersion >= 3) { + uri = PreviewColumns.COMPONENTS_URI; + // Load specified wallpaper previews (component_id is specified) + selection += " AND " + PreviewColumns.COMPONENT_ID + "=?"; + selectionArgs = new String[]{pkgName, String.valueOf(componentId)}; + projection = new String[]{ + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.WALLPAPER_PREVIEW, + PreviewColumns.COMPONENT_ID + }; + } else { + projection = new String[]{ + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.WALLPAPER_PREVIEW + }; + } + break; + case LOADER_ID_NAVIGATION_BAR: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.STATUSBAR_BACKGROUND, + PreviewColumns.NAVBAR_BACK_BUTTON, + PreviewColumns.NAVBAR_HOME_BUTTON, + PreviewColumns.NAVBAR_RECENT_BUTTON + }; + break; + case LOADER_ID_LOCKSCREEN: + projection = new String[]{ + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.LOCK_WALLPAPER_PREVIEW, + }; + break; + case LOADER_ID_LIVE_LOCK_SCREEN: + projection = new String[]{ + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + ThemesColumns.MODIFIES_LIVE_LOCK_SCREEN, + PreviewColumns.LIVE_LOCK_SCREEN_PREVIEW + }; + break; + case LOADER_ID_STYLE: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE, + PreviewColumns.STYLE_PREVIEW + }; + break; + case LOADER_ID_BOOT_ANIMATION: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE + }; + break; + case LOADER_ID_RINGTONE: + case LOADER_ID_NOTIFICATION: + case LOADER_ID_ALARM: + projection = new String[] { + ThemesColumns.PKG_NAME, + ThemesColumns.TITLE + }; + break; + } + return new CursorLoader(context, uri, projection, selection, selectionArgs, null); + } + + public static Object[] getRowFromCursor(Cursor cursor) { + Object[] row = null; + if (cursor != null) { + int colCount = cursor.getColumnCount(); + row = new Object[colCount]; + for (int indx = 0; indx < colCount; indx++) { + row[indx] = getFieldValueFromRow(cursor, indx); + } + } + return row; + } + + public static Object getFieldValueFromRow(Cursor cursor, int position) { + switch (cursor.getType(position)) { + case Cursor.FIELD_TYPE_BLOB: return cursor.getBlob(position); + case Cursor.FIELD_TYPE_FLOAT: return cursor.getFloat(position); + case Cursor.FIELD_TYPE_INTEGER: return cursor.getInt(position); + case Cursor.FIELD_TYPE_STRING: return cursor.getString(position); + case Cursor.FIELD_TYPE_NULL: + default: + return null; + } + } +} \ No newline at end of file diff --git a/src/org/cyanogenmod/theme/util/FontConfigParser.java b/src/org/cyanogenmod/theme/util/FontConfigParser.java new file mode 100644 index 0000000..a5b4de9 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/FontConfigParser.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * Parses an XML font config. Example: + * + * + * + * + * + * sans-serif + * arial + * + * + * Roboto-Regular.ttf + * Roboto-Bold.ttf + * Roboto-Italic.ttf + * Roboto-BoldItalic.ttf + * + * + * + * ... + * + * + */ +public class FontConfigParser { + + public static class Family { + public List nameset = new ArrayList(); + public List fileset = new ArrayList(); + } + + public static List parse(InputStream in) throws XmlPullParserException, IOException { + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(in, null); + parser.nextTag(); + return readFamilySet(parser); + } finally { + in.close(); + } + } + + private static List readFamilySet(XmlPullParser parser) throws XmlPullParserException, IOException { + List families = new ArrayList(); + parser.require(XmlPullParser.START_TAG, null, "familyset"); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + + // Starts by looking for the entry tag + if (name.equals("family")) { + Family family = readFamily(parser); + families.add(family); + } + } + return families; + } + + private static Family readFamily(XmlPullParser parser) throws XmlPullParserException, IOException { + Family family = new Family(); + parser.require(XmlPullParser.START_TAG, null, "family"); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + if (name.equals("nameset")) { + List nameset = readNameset(parser); + family.nameset = nameset; + } else if (name.equals("fileset")) { + List fileset = readFileset(parser); + family.fileset = fileset; + } else { + skip(parser); + } + } + return family; + } + + private static List readNameset(XmlPullParser parser) throws XmlPullParserException, IOException { + List names = new ArrayList(); + parser.require(XmlPullParser.START_TAG, null, "nameset"); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String tagname = parser.getName(); + if (tagname.equals("name")) { + String name = readText(parser); + names.add(name); + } else { + skip(parser); + } + } + return names; + } + + private static List readFileset(XmlPullParser parser) throws XmlPullParserException, IOException { + List files = new ArrayList(); + parser.require(XmlPullParser.START_TAG, null, "fileset"); + + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + if (name.equals("file")) { + String file = readText(parser); + files.add(file); + } else { + skip(parser); + } + } + return files; + } + + // For the tags title and summary, extracts their text values. + private static String readText(XmlPullParser parser) throws IOException, XmlPullParserException { + String result = ""; + if (parser.next() == XmlPullParser.TEXT) { + result = parser.getText(); + parser.nextTag(); + } + return result; + } + + private static void skip(XmlPullParser parser) throws XmlPullParserException, IOException { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (parser.next()) { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } +} diff --git a/src/org/cyanogenmod/theme/util/IconPreviewHelper.java b/src/org/cyanogenmod/theme/util/IconPreviewHelper.java new file mode 100644 index 0000000..3f5b897 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/IconPreviewHelper.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.app.ActivityManager; +import android.app.ComposedIconInfo; +import android.app.IconPackHelper; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.SparseArray; + +/** + * This class handles all the logic to build a preview icon + * If the system currently has a theme applied we do NOT + * want this code to be impacted by it. So code in this + * class creates special "no theme attached" resource objects + * to retrieve objects from. + */ +public class IconPreviewHelper { + private static final String TAG = IconPreviewHelper.class.getSimpleName(); + private final static float ICON_SCALE_FACTOR = 1.3f; //Arbitrary. Looks good + + private Context mContext; + private DisplayMetrics mDisplayMetrics; + private Configuration mConfiguration; + private int mIconDpi = 0; + private String mThemePkgName; + private IconPackHelper mIconPackHelper; + private int mIconSize; + + /** + * @param themePkgName - The package name of the theme we wish to preview + */ + public IconPreviewHelper(Context context, String themePkgName) { + mContext = context; + mDisplayMetrics = context.getResources().getDisplayMetrics(); + mConfiguration = context.getResources().getConfiguration(); + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + mIconDpi = (int) (am.getLauncherLargeIconDensity() * ICON_SCALE_FACTOR); + mThemePkgName = themePkgName; + mIconPackHelper = new IconPackHelper(mContext); + try { + mIconPackHelper.loadIconPack(mThemePkgName); + } catch (NameNotFoundException e) {} + mIconSize = (int) (am.getLauncherLargeIconSize() * ICON_SCALE_FACTOR); + } + + /** + * Returns the actual label name for a given component + * If the activity does not have a label it will return app's label + * If neither has a label returns empty string + */ + public String getLabel(ComponentName component) { + String label = ""; + try { + PackageManager pm = mContext.getPackageManager(); + ApplicationInfo appInfo = pm.getApplicationInfo(component.getPackageName(), 0); + ActivityInfo activityInfo = pm.getActivityInfo(component, 0); + + AssetManager assets = new AssetManager(); + assets.addAssetPath(appInfo.publicSourceDir); + Resources res = new Resources(assets, mDisplayMetrics, mConfiguration); + + if (activityInfo.labelRes != 0) { + label = res.getString(activityInfo.labelRes); + } else if (appInfo.labelRes != 0) { + label = res.getString(appInfo.labelRes); + } + } catch(NameNotFoundException exception) { + Log.e(TAG, "unable to find pkg for " + component.toString()); + } + return label; + } + + /** + * Returns the icon for the given component regardless of the system's + * currently applied theme. If the preview theme does not support the icon, then + * return the system default icon. + */ + public Drawable getIcon(ComponentName component) { + String packageName = component.getPackageName(); + String activityName = component.getClassName(); + Drawable icon = getThemedIcon(packageName, activityName); + if (icon == null) { + icon = getDefaultIcon(packageName, activityName); + } + if (icon != null) { + icon.setBounds(0, 0, mIconSize, mIconSize); + } + return icon; + } + + private Drawable getThemedIcon(String pkgName, String activityName) { + Drawable drawable = null; + ActivityInfo info = new ActivityInfo(); + info.packageName = pkgName; + info.name = activityName; + drawable = mIconPackHelper.getDrawableForActivityWithDensity(info, mIconDpi); + + return drawable; + } + + /** + * Returns the default icon. This can be the normal icon associated with the app or a composed + * icon if the icon pack supports background, mask, and/or foreground. + * @param pkgName + * @param activityName + * @return + */ + public Drawable getDefaultIcon(String pkgName, String activityName) { + Drawable drawable = null; + ComponentName component = new ComponentName(pkgName, activityName); + PackageManager pm = mContext.getPackageManager(); + Resources res = null; + try { + ActivityInfo info = pm.getActivityInfo(component, 0); + ApplicationInfo appInfo = pm.getApplicationInfo(pkgName, 0); + + AssetManager assets = new AssetManager(); + assets.addAssetPath(appInfo.publicSourceDir); + res = new Resources(assets, mDisplayMetrics, mConfiguration); + + final int iconId = info.icon != 0 ? info.icon : appInfo.icon; + info.themedIcon = 0; + setupComposedIcon(res, info, iconId); + drawable = getFullResIcon(res, iconId); + } catch (NameNotFoundException e2) { + Log.w(TAG, "Unable to get the icon for " + pkgName + " using default"); + } + drawable = (drawable != null) ? + getComposedIcon(res, drawable) : getFullResDefaultActivityIcon(); + return drawable; + } + + private Drawable getComposedIcon(Resources res, Drawable baseIcon) { + ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo(); + if (res != null && iconInfo != null && (iconInfo.iconBacks != null || + iconInfo.iconMask != 0 || iconInfo.iconUpon != 0)) { + return IconPackHelper.IconCustomizer.getComposedIconDrawable(baseIcon, res, iconInfo); + } + return baseIcon; + } + + private void setupComposedIcon(Resources res, ActivityInfo info, int iconId) { + ComposedIconInfo iconInfo = mIconPackHelper.getComposedIconInfo(); + if (iconInfo.iconBacks == null && iconInfo.iconMask == 0 && iconInfo.iconUpon == 0) { + return; + } + + res.setComposedIconInfo(iconInfo); + + SparseArray icons = new SparseArray(1); + info.themedIcon = 0; + icons.put(iconId, info); + res.setIconResources(icons); + } + + private Drawable getFullResIcon(Resources resources, int iconId) { + Drawable d; + try { + d = resources.getDrawableForDensity(iconId, mIconDpi, null, false); + } catch (Resources.NotFoundException e) { + d = null; + } + return (d != null) ? d : getFullResDefaultActivityIcon(); + } + + private Drawable getFullResDefaultActivityIcon() { + return getFullResIcon(Resources.getSystem(), android.R.mipmap.sym_def_app_icon); + } +} diff --git a/src/org/cyanogenmod/theme/util/NotificationHelper.java b/src/org/cyanogenmod/theme/util/NotificationHelper.java new file mode 100644 index 0000000..ebbdb66 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/NotificationHelper.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.BitmapFactory; +import android.text.TextUtils; + +import org.cyanogenmod.theme.chooser.ChooserActivity; +import org.cyanogenmod.theme.chooser.R; + +public class NotificationHelper { + private static final int NOTIFICATION_ID = 0x434D5443; + + public static void postThemeInstalledNotification(Context context, String pkgName) { + String themeName = null; + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0); + if (pi.themeInfo != null) { + themeName = pi.themeInfo.name; + } + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return; + } + if (TextUtils.isEmpty(themeName)) { + return; + } + + int themeCount = PreferenceUtils.getNewlyInstalledThemeCount(context) + 1; + + Intent intent = new Intent(context, ChooserActivity.class); + intent.setAction(Intent.ACTION_MAIN); + intent.putExtra("pkgName", pkgName); + PendingIntent pi = PendingIntent.getActivity(context, 0, intent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT); + + String title = null; + String content = null; + final Resources res = context.getResources(); + if (themeCount == 1) { + title = String.format(res.getString( + R.string.theme_installed_notification_title), themeName); + content = res.getString(R.string.theme_installed_notification_text); + } else { + title = String.format(res.getString(R.string.themes_installed_notification_title), + themeCount); + content = String.format(res.getQuantityString( + R.plurals.themes_installed_notification_text, themeCount -1), + themeName, themeCount - 1); + } + NotificationManager nm = + (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + Notification notice = new Notification.Builder(context) + .setAutoCancel(true) + .setOngoing(false) + .setContentTitle(title) + .setContentText(content) + .setContentIntent(pi) + .setSmallIcon(R.drawable.ic_notify) + .setWhen(System.currentTimeMillis()) + .build(); + if (themeCount > 1) notice.number = themeCount; + nm.notify(NOTIFICATION_ID, notice); + PreferenceUtils.setNewlyInstalledThemeCount(context, themeCount); + } + + public static void cancelNotifications(Context context) { + NotificationManager nm = (NotificationManager) + context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel(NOTIFICATION_ID); + PreferenceUtils.setNewlyInstalledThemeCount(context, 0); + } +} diff --git a/src/org/cyanogenmod/theme/util/PreferenceUtils.java b/src/org/cyanogenmod/theme/util/PreferenceUtils.java new file mode 100644 index 0000000..080615f --- /dev/null +++ b/src/org/cyanogenmod/theme/util/PreferenceUtils.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.ThemeUtils; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.text.TextUtils; +import android.util.Log; + +import java.util.HashSet; +import java.util.Set; + +public class PreferenceUtils { + private static final String TAG = PreferenceUtils.class.getSimpleName(); + + public static final String PREF_APPLIED_BASE_THEME = "applied_base_theme"; + public static final String PREF_UPDATED_THEMES = "updated_themes"; + public static final String PREF_NEWLY_INSTALLED_THEME_COUNT = "newly_installed_theme_count"; + public static final String PREF_INSTALLED_THEMES_PROCESSING = "installed_themes_processing"; + public static final String PREF_SHOW_PER_APP_THEMING_NEW_TAG = "show_per_app_new_tag"; + + public static SharedPreferences getSharedPreferences(Context context) { + if (context == null) return null; + return context.getSharedPreferences(context.getPackageName(), Context.MODE_PRIVATE); + } + + public static String getAppliedBaseTheme(Context context) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs == null) return null; + + final Resources res = context.getResources(); + final ThemeConfig config = res.getConfiguration().themeConfig; + String appliedTheme = config != null + ? config.getOverlayPkgName() + : ThemeConfig.SYSTEM_DEFAULT; + return prefs.getString(PREF_APPLIED_BASE_THEME, appliedTheme); + } + + public static void setAppliedBaseTheme(Context context, String pkgName) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + prefs.edit().putString(PREF_APPLIED_BASE_THEME, pkgName).apply(); + } + } + + public static Set getUpdatedThemes(Context context) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs == null) return null; + + return prefs.getStringSet(PREF_UPDATED_THEMES, null); + } + + public static void addUpdatedTheme(Context context, String pkgName) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + Set updatedThemes = new HashSet(); + Set current = prefs.getStringSet(PREF_UPDATED_THEMES, null); + if (current != null) { + updatedThemes.addAll(current); + } + if (updatedThemes.add(pkgName)) { + prefs.edit().putStringSet(PREF_UPDATED_THEMES, updatedThemes).apply(); + } + } + } + + public static void removeUpdatedTheme(Context context, String pkgName) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + Set updatedThemes = new HashSet(); + Set current = prefs.getStringSet(PREF_UPDATED_THEMES, null); + if (current != null) { + updatedThemes.addAll(current); + } + if (updatedThemes.remove(pkgName)) { + prefs.edit().putStringSet(PREF_UPDATED_THEMES, updatedThemes).apply(); + } + } + } + + public static boolean hasThemeBeenUpdated(Context context, String pkgName) { + Set updatedThemes = getUpdatedThemes(context); + return updatedThemes != null && updatedThemes.contains(pkgName); + } + + public static int getNewlyInstalledThemeCount(Context context) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs == null) return 0; + + return prefs.getInt(PREF_NEWLY_INSTALLED_THEME_COUNT, 0); + } + + public static void setNewlyInstalledThemeCount(Context context, int count) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + prefs.edit().putInt(PREF_NEWLY_INSTALLED_THEME_COUNT, count).apply(); + } + } + + public static boolean getShowPerAppThemeNewTag(Context context) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + return prefs.getBoolean(PREF_SHOW_PER_APP_THEMING_NEW_TAG, true); + } + + return false; + } + + public static void setShowPerAppThemeNewTag(Context context, boolean show) { + SharedPreferences prefs = getSharedPreferences(context); + if (prefs != null) { + prefs.edit().putBoolean(PREF_SHOW_PER_APP_THEMING_NEW_TAG, show).apply(); + } + } +} diff --git a/src/org/cyanogenmod/theme/util/ThemedTypefaceHelper.java b/src/org/cyanogenmod/theme/util/ThemedTypefaceHelper.java new file mode 100644 index 0000000..cf5ddfc --- /dev/null +++ b/src/org/cyanogenmod/theme/util/ThemedTypefaceHelper.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.content.Context; +import android.content.res.AssetManager; +import android.graphics.FontListParser; +import android.graphics.FontListParser.Family; +import android.graphics.Typeface; +import android.util.Log; + +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.List; + +/** + * Assists in loading a themes font typefaces. + * Will load system default if there is a load issue + */ +public class ThemedTypefaceHelper { + private static final String TAG = ThemedTypefaceHelper.class.getName(); + private static final String FAMILY_SANS_SERIF = "sans-serif"; + private static final String FONTS_DIR = "fonts"; + private static final String SYSTEM_FONTS_XML = "/system/etc/system_fonts.xml"; + private static final String SYSTEM_FONTS_DIR = "/system/fonts"; + + private boolean mIsLoaded; + private Context mThemeContext; + private List mFamilies; + private Typeface[] mTypefaces = new Typeface[4]; + + public void load(Context context, String pkgName) { + try { + loadThemedFonts(context, pkgName); + return; + } catch(Exception e) { + Log.w(TAG, "Unable to parse and load themed fonts for " + pkgName + + ". Falling back to system fonts", e ); + } + + try { + loadSystemFonts(); + return; + } catch(Exception e) { + Log.e(TAG, "Parsing system fonts failed. Falling back to Typeface loaded fonts", e); + } + + // There is no reason for this to happen unless someone + // messed up the system_fonts.xml + loadDefaultFonts(); + } + + private void loadThemedFonts(Context context, String pkgName) throws Exception { + //Parse the font XML + mThemeContext = context.createPackageContext(pkgName, Context.CONTEXT_IGNORE_SECURITY); + AssetManager assetManager = mThemeContext.getAssets(); + InputStream is = assetManager.open(FONTS_DIR + File.separator + ThemeUtils.FONT_XML); + FontListParser.Config fontConfig = FontListParser.parse(is, FONTS_DIR); + mFamilies = fontConfig.families; + + //Load the typefaces for sans-serif + Family sanSerif = getFamily(FAMILY_SANS_SERIF); + mTypefaces[Typeface.NORMAL] = loadTypeface(sanSerif, Typeface.NORMAL); + mTypefaces[Typeface.BOLD] = loadTypeface(sanSerif, Typeface.BOLD); + mTypefaces[Typeface.ITALIC] = loadTypeface(sanSerif, Typeface.ITALIC); + mTypefaces[Typeface.BOLD_ITALIC] = loadTypeface(sanSerif, Typeface.BOLD_ITALIC); + mIsLoaded = true; + } + + private void loadSystemFonts() throws Exception { + //Parse the system font XML + File file = new File(SYSTEM_FONTS_XML); + InputStream is = new FileInputStream(file); + FontListParser.Config fontConfig = FontListParser.parse(is, SYSTEM_FONTS_DIR); + mFamilies = fontConfig.families; + + //Load the typefaces for sans-serif + Family sanSerif = getFamily(FAMILY_SANS_SERIF); + if (mTypefaces[Typeface.NORMAL] == null) { + mTypefaces[Typeface.NORMAL] = loadSystemTypeface(sanSerif, Typeface.NORMAL); + } + if (mTypefaces[Typeface.BOLD] == null) { + mTypefaces[Typeface.BOLD] = loadSystemTypeface(sanSerif, Typeface.BOLD); + } + if (mTypefaces[Typeface.ITALIC] == null) { + mTypefaces[Typeface.ITALIC] = loadSystemTypeface(sanSerif, Typeface.ITALIC); + } + if (mTypefaces[Typeface.BOLD_ITALIC] == null) { + mTypefaces[Typeface.BOLD_ITALIC] = loadSystemTypeface(sanSerif, Typeface.BOLD_ITALIC); + } + mIsLoaded = true; + } + + private void loadDefaultFonts() { + mTypefaces[Typeface.NORMAL] = Typeface.DEFAULT; + mTypefaces[Typeface.BOLD] = Typeface.DEFAULT_BOLD; + mIsLoaded = true; + } + + private Family getFamily(String familyName) throws Exception { + for(Family family : mFamilies) { + if (family.name.equals(familyName)) { + return family; + } + } + throw new Exception("Unable to find " + familyName); + } + + private Typeface loadTypeface(Family family, int style) { + AssetManager assets = mThemeContext.getAssets(); + String path = family.fonts.get(style).fontName; + return Typeface.createFromAsset(assets, path); + } + + private Typeface loadSystemTypeface(Family family, int style) { + return Typeface.createFromFile(family.fonts.get(style).fontName); + } + + public Typeface getTypeface(int style) { + if (!mIsLoaded) throw new IllegalStateException("Helper was not loaded"); + return mTypefaces[style]; + } +} diff --git a/src/org/cyanogenmod/theme/util/TypefaceHelperCache.java b/src/org/cyanogenmod/theme/util/TypefaceHelperCache.java new file mode 100644 index 0000000..76183f8 --- /dev/null +++ b/src/org/cyanogenmod/theme/util/TypefaceHelperCache.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.content.Context; + +import java.util.HashMap; +import java.util.Map; + +public class TypefaceHelperCache { + private static TypefaceHelperCache sHelperCache; + private final Map mCache; + + private TypefaceHelperCache() { + mCache = new HashMap(); + } + + public static synchronized TypefaceHelperCache getInstance() { + if (sHelperCache == null) { + sHelperCache = new TypefaceHelperCache(); + } + return sHelperCache; + } + + public ThemedTypefaceHelper getHelperForTheme(Context context, String pkgName) { + synchronized (mCache) { + ThemedTypefaceHelper helper = mCache.get(pkgName); + if (helper == null) { + helper = new ThemedTypefaceHelper(); + helper.load(context, pkgName); + mCache.put(pkgName, helper); + } + return helper; + } + } + + public int getTypefaceCount() { + synchronized (mCache) { + return mCache.size(); + } + } +} diff --git a/src/org/cyanogenmod/theme/util/Utils.java b/src/org/cyanogenmod/theme/util/Utils.java new file mode 100644 index 0000000..94afecb --- /dev/null +++ b/src/org/cyanogenmod/theme/util/Utils.java @@ -0,0 +1,724 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.app.ActivityManager; +import android.app.WallpaperManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PermissionInfo; +import android.content.pm.ResolveInfo; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.ThemeConfig; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Point; +import android.graphics.Rect; +import android.os.RemoteException; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.TypedValue; +import android.view.IWindowManager; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; + +import org.cyanogenmod.theme.chooser.ChooserActivity; + +import cyanogenmod.externalviews.KeyguardExternalView; +import cyanogenmod.providers.CMSettings; +import cyanogenmod.providers.ThemesContract; + +import org.cyanogenmod.internal.util.ThemeUtils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.security.InvalidParameterException; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static android.content.res.ThemeConfig.SYSTEM_DEFAULT; + +public class Utils { + private static final String TAG = Utils.class.getSimpleName(); + private static final boolean DEBUG = false; + + private static final String OVERLAY_BASE_PATH = "overlays" + File.separator; + + public static Bitmap decodeFile(String path, int reqWidth, int reqHeight) { + BitmapFactory.Options opts = new BitmapFactory.Options(); + + // Determine insample size + opts.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, opts); + opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); + + // Decode the bitmap, regionally if necessary + Bitmap bitmap = null; + opts.inJustDecodeBounds = false; + Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); + try { + if (rect != null) { + BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(path, false); + // Check if we can downsample more now that we cropped + opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), + reqWidth, reqHeight); + bitmap = decoder.decodeRegion(rect, opts); + } else { + bitmap = BitmapFactory.decodeFile(path, opts); + } + } catch (IOException e) { + Log.e(TAG, "Unable to open resource in path" + path, e); + } + return bitmap; + } + + public static Bitmap decodeResource(Resources res, int resId, int reqWidth, int reqHeight) { + BitmapFactory.Options opts = new BitmapFactory.Options(); + + // Determine insample size + opts.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, opts); + opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); + + // Decode the bitmap, regionally if necessary + Bitmap bitmap = null; + opts.inJustDecodeBounds = false; + Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); + + InputStream stream = null; + try { + if (rect != null) { + stream = res.openRawResource(resId, new TypedValue()); + if (stream == null) return null; + BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(stream, false); + // Check if we can downsample a little more now that we cropped + opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), + reqWidth, reqHeight); + bitmap = decoder.decodeRegion(rect, opts); + } else { + bitmap = BitmapFactory.decodeResource(res, resId, opts); + } + } catch (IOException e) { + Log.e(TAG, "Unable to open resource " + resId, e); + } finally { + closeQuiet(stream); + } + return bitmap; + } + + + public static Bitmap getBitmapFromAsset(Context ctx, String path,int reqWidth, int reqHeight) { + if (ctx == null || path == null) + return null; + + String ASSET_BASE = "file:///android_asset/"; + path = path.substring(ASSET_BASE.length()); + + + Bitmap bitmap = null; + try { + AssetManager assets = ctx.getAssets(); + InputStream is = assets.open(path); + + // Determine insample size + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inJustDecodeBounds = true; + BitmapFactory.decodeStream(is, null, opts); + opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight); + is.close(); + + // Decode the bitmap, regionally if neccessary + is = assets.open(path); + opts.inJustDecodeBounds = false; + Rect rect = getCropRectIfNecessary(opts, reqWidth, reqHeight); + if (rect != null) { + BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is, false); + // Check if we can downsample a little more now that we cropped + opts.inSampleSize = calculateInSampleSize(rect.width(), rect.height(), + reqWidth, reqHeight); + bitmap = decoder.decodeRegion(rect, opts); + } else { + bitmap = BitmapFactory.decodeStream(is); + } + } catch (IOException e) { + e.printStackTrace(); + } + return bitmap; + } + + + /** + * For excessively large images with an awkward ratio we + * will want to crop them + * @return + */ + public static Rect getCropRectIfNecessary( + BitmapFactory.Options options,int reqWidth, int reqHeight) { + Rect rect = null; + // Determine downsampled size + int width = options.outWidth / options.inSampleSize; + int height = options.outHeight / options.inSampleSize; + + if ((reqHeight * 1.5 < height)) { + int bottom = height/ 4; + int top = bottom + height/2; + rect = new Rect(0, bottom, width, top); + } else if ((reqWidth * 1.5 < width)) { + int left = width / 4; + int right = left + height/2; + rect = new Rect(left, 0, right, height); + } + return rect; + } + + public static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + return calculateInSampleSize(options.outWidth, options.outHeight, reqWidth, reqHeight); + } + + // Modified from original source: + // http://developer.android.com/training/displaying-bitmaps/load-bitmap.html + public static int calculateInSampleSize( + int decodeWidth, int decodeHeight, int reqWidth, int reqHeight) { + // Raw height and width of image + int inSampleSize = 1; + + if (decodeHeight > reqHeight || decodeWidth > reqWidth) { + final int halfHeight = decodeHeight / 2; + final int halfWidth = decodeWidth / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight && + (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + public static InputStream getInputStreamFromAsset( + Context ctx, String path) throws IOException { + if (ctx == null || path == null) + return null; + InputStream is = null; + String ASSET_BASE = "file:///android_asset/"; + path = path.substring(ASSET_BASE.length()); + AssetManager assets = ctx.getAssets(); + is = assets.open(path); + return is; + } + + public static void copy(InputStream is, OutputStream os) throws IOException { + final byte[] bytes = new byte[4096]; + int len; + while ((len = is.read(bytes)) > 0) { + os.write(bytes, 0, len); + } + } + + public static void closeQuiet(InputStream stream) { + if (stream == null) + return; + try { + stream.close(); + } catch (IOException e) { + } + } + + public static void closeQuiet(OutputStream stream) { + if (stream == null) + return; + try { + stream.close(); + } catch (IOException e) { + } + } + + //Note: will not delete populated subdirs + public static void deleteFilesInDir(String dirPath) { + File fontDir = new File(dirPath); + File[] files = fontDir.listFiles(); + if (files != null) { + for(File file : fontDir.listFiles()) { + file.delete(); + } + } + } + + public static boolean hasNavigationBar(Context context) { + boolean needsNavigationBar = false; + try { + IWindowManager wm = WindowManagerGlobal.getWindowManagerService(); + needsNavigationBar = wm.needsNavigationBar(); + } catch (RemoteException e) { + } + // Need to also check for devices with hardware keys where the user has chosen to use + // the on screen navigation bar + needsNavigationBar = needsNavigationBar || + CMSettings.Secure.getInt(context.getContentResolver(), + CMSettings.Secure.DEV_FORCE_SHOW_NAVBAR, 0) == 1; + return needsNavigationBar; + } + + public static Bitmap loadBitmapBlob(Cursor cursor, int columnIdx) { + if (columnIdx < 0) { + Log.w(TAG, "loadBitmapBlob(): Invalid index provided, returning null"); + return null; + } + + if (cursor.getType(columnIdx) == Cursor.FIELD_TYPE_STRING) { + return loadBitmapFile(cursor, columnIdx); + } + + byte[] blob = cursor.getBlob(columnIdx); + if (blob == null) return null; + return BitmapFactory.decodeByteArray(blob, 0, blob.length); + } + + public static Bitmap loadBitmapFile(Cursor cursor, int columnIdx) { + if (columnIdx < 0) { + Log.w(TAG, "loadBitmapFile(): Invalid index provided, returning null"); + return null; + } + String path = cursor.getString(columnIdx); + if (TextUtils.isEmpty(path)) { + return null; + } + + Bitmap image = null; + FileInputStream inputStream; + try { + inputStream = new FileInputStream(path); + image = BitmapFactory.decodeStream(inputStream); + inputStream.close(); + } catch (Exception e) { + Log.w(TAG, "Unable to open preview " + path, e); + } + + return image; + } + + public static String getBatteryIndex(int type) { + switch(type) { + case 2: + return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_CIRCLE; + case 5: + return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_LANDSCAPE; + default: + return ThemesContract.PreviewColumns.STATUSBAR_BATTERY_PORTRAIT; + } + } + + public static Bitmap getRegularWallpaperBitmap(Context context) { + WallpaperManager wm = WallpaperManager.getInstance(context); + + Bitmap bitmap = null; + // desktop wallpaper here + Bitmap wallpaper = wm.getBitmap(); + if (wallpaper == null) { + return null; + } + + Point size = new Point(); + WindowManager windowManager = + (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + windowManager.getDefaultDisplay().getRealSize(size); + + final int dw = size.x; + final int dh = size.y; + + // Center the scaled image + float scale = Math.max(1f, Math.max(dw / (float) wallpaper.getWidth(), + dh / (float) wallpaper.getHeight())); + + final int scaledWidth = Math.round((wallpaper.getWidth() * scale)); + final int scaledHeight = Math.round((wallpaper.getHeight() * scale)); + + // TODO: set xOffset to wm.getLastWallpaperX() once available + int xOffset = wm.getLastWallpaperX(); + // x offset + if (xOffset == -1) { + xOffset = 0; + } else { + xOffset *= -1; + } + + // y offsets + // TODO: set yOffset to wm.getLastWallpaperY() once available + int yOffset = wm.getLastWallpaperY(); + if (yOffset == -1) { + yOffset = 0; + } else { + yOffset *= -1; + } + + if (DEBUG) { + Log.d(TAG, "scale: " + scale); + Log.d(TAG, "scaledWidth: " + scaledWidth); + Log.d(TAG, "scaledHeight: " + scaledHeight); + Log.d(TAG, "wallpaper size: width: " + wallpaper.getWidth() + + ", height: " + wallpaper.getHeight()); + Log.d(TAG, "xOffset: " + xOffset); + Log.d(TAG, "yOffset: " + yOffset); + } + + try { + if (wallpaper.getHeight() < dh) { + // need to scale it up vertically + + if (wallpaper.getHeight() > wallpaper.getWidth()) { + // handle portrait wallpaper + float diff = scaledWidth - dw; + int diffhalf = Math.round(diff / 2); + + bitmap = Bitmap.createScaledBitmap(wallpaper, scaledWidth, scaledHeight, true); + bitmap = Bitmap.createBitmap(bitmap, diffhalf, 0, dw, dh); + bitmap = Bitmap.createBitmap(bitmap, xOffset, 0, dw, dh); + } else { + int goldenWidth = Math.round(wallpaper.getHeight() * 1.125f); + int spaceA = (wallpaper.getWidth() - goldenWidth) / 2; + int spaceB = (goldenWidth - Math.round(dh / scale)) / 2; + + bitmap = Bitmap.createBitmap(wallpaper, spaceA, 0, goldenWidth, + wallpaper.getHeight()); + int left = spaceB + Math.round(xOffset / scale); + bitmap = Bitmap.createBitmap(bitmap, left, 0, Math.round(dw / scale), + Math.round(dh / scale)); + } + + } else if (wallpaper.getWidth() < dw) { + // need to scale it up horizontally + + if (wallpaper.getHeight() > wallpaper.getWidth()) { + // handle portrait wallpaper + return wallpaper; + + } else { + // handle landscape wallpaper + float diff = wallpaper.getHeight() - wallpaper.getWidth(); + int diffhalf = Math.round(diff / 2); + + if (diffhalf < 0) { + return wallpaper; + } + + bitmap = Bitmap.createBitmap( + wallpaper, diffhalf, 0, + wallpaper.getWidth(), wallpaper.getWidth()); + + // blow it up + bitmap = Bitmap.createScaledBitmap(bitmap, scaledWidth, scaledWidth, true); + + bitmap = Bitmap.createBitmap(bitmap, 0, 0, dw, dh); + } + + } else { + // sometimes the wallpaper manager gives incorrect offsets, + // and adds like 200 pixels randomly. If it's bigger than we can handle, calculate + // our own :) + if (yOffset + dh > wallpaper.getHeight()) { + yOffset = (wallpaper.getHeight() - dh) / 2; + } + if (xOffset + dw > wallpaper.getWidth()) { + yOffset = (wallpaper.getWidth() - dw) / 2; + } + bitmap = Bitmap.createBitmap(wallpaper, xOffset, yOffset, dw, dh); + } + } catch (IllegalArgumentException e) { + // Cropping/resizing failed so return the original + bitmap = wallpaper; + } + return bitmap; + } + + public static boolean isRecentTaskHome(Context context) { + final ActivityManager am = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + final List recentTasks = am.getRecentTasks( + 2, ActivityManager.RECENT_IGNORE_UNAVAILABLE); + if (recentTasks.size() > 1) { + ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(1); + + Intent intent = new Intent(recentInfo.baseIntent); + if (recentInfo.origActivity != null) { + intent.setComponent(recentInfo.origActivity); + } + + // Now check if this recent task is a launcher + if (isCurrentHomeActivity(context, intent.getComponent())) { + return true; + } + } + return false; + } + + public static boolean isRecentTaskThemeStore(Context context) { + final ActivityManager am = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + + final List recentTasks = am.getRecentTasks( + 2, ActivityManager.RECENT_IGNORE_UNAVAILABLE); + if (recentTasks.size() > 0) { + ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); + + Intent intent = new Intent(recentInfo.baseIntent); + if (recentInfo.origActivity != null) { + intent.setComponent(recentInfo.origActivity); + } + + if (intent.getComponent() + .getPackageName().equals(ChooserActivity.THEME_STORE_PACKAGE)) { + return true; + } + } + return false; + } + + + public static String getTopTaskPackageName(Context context) { + final ActivityManager am = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + final List recentTasks = am.getRecentTasks(1, 0); + if (recentTasks.size() > 0) { + ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(0); + if (recentInfo.origActivity != null) { + return recentInfo.origActivity.getPackageName(); + } + if (recentInfo.baseIntent != null) { + return recentInfo.baseIntent.getComponent().getPackageName(); + } + } + return null; + } + + public static boolean hasPerAppThemesApplied(Context context) { + final Configuration config = context.getResources().getConfiguration(); + final ThemeConfig themeConfig = config != null ? config.themeConfig : null; + if (themeConfig != null) { + Map themes = themeConfig.getAppThemes(); + for (String appPkgName : themes.keySet()) { + if (ThemeUtils.isPerAppThemeComponent(appPkgName)) { + return true; + } + } + } + return false; + } + + /** + * Method to identify if a theme explicitly overlays a particular app. Explicit is defined + * as having files in overlays/appPkgName/ + * @param context + * @param appPkgNane + * @param themePkgName + * @return + */ + public static boolean themeHasOverlayForApp(Context context, String appPkgNane, + String themePkgName) { + boolean hasExplicitOverlay = false; + if (ThemeConfig.SYSTEM_DEFAULT.equals(themePkgName)) { + hasExplicitOverlay = true; + } else { + try { + Context themeContext = context.createPackageContext(themePkgName, 0); + if (themeContext != null) { + AssetManager assets = themeContext.getAssets(); + String[] files = assets.list(OVERLAY_BASE_PATH + appPkgNane); + if (files != null && files.length > 0) hasExplicitOverlay = true; + } + } catch (Exception e) { + // don't care, we'll return false and let the caller handle things + } + } + return hasExplicitOverlay; + } + + private static boolean isCurrentHomeActivity(Context context, + ComponentName component) { + final PackageManager pm = context.getPackageManager(); + ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME) + .resolveActivityInfo(pm, 0); + + return homeInfo != null + && homeInfo.packageName.equals(component.getPackageName()) + && homeInfo.name.equals(component.getClassName()); + } + + /** + * Returns the resource-IDs for all attributes specified in the given + * -resource tag as an int array. + * stackoverflow.com/questions/13816596/accessing-declare-styleable-resources-programatically + * + * @param name + * @return + */ + public static final int[] getResourceDeclareStyleableIntArray(String pkgName, String name) { + try { + //use reflection to access the resource class + Field[] fields2 = + Class.forName(pkgName + ".R$styleable").getFields(); + + //browse all fields + for (Field f : fields2) { + //pick matching field + if (f.getName().equals(name)) { + //return as int array + int[] ret = (int[])f.get(null); + return ret; + } + } + } + catch (Throwable t) { + } + + return null; + } + + /** + * Retrieves the list of dangerous permissions not granted to the supplied package. This method + * is not capable of identifying if a given permission was previously revoked by the user or + * if the user decided not to be asked again. + * + * @param context + * @param pkgName + * @return Returns an array of Strings with the name of the permissions. An empty array will + * be returned if all dangerous permissions have been already granted. + */ + public static String[] getDangerousPermissionsNotGranted(Context context, String pkgName) + throws InvalidParameterException { + LinkedList permissionsNotGranted = new LinkedList(); + PackageInfo pkgInfo = null; + PackageManager pm = context.getPackageManager(); + try { + pkgInfo = pm.getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS); + } catch (PackageManager.NameNotFoundException e) { + throw new InvalidParameterException("Package " + pkgName + " not found"); + } + + String[] requestedPermissions = pkgInfo.requestedPermissions; + int[] requestedPermissionsFlags = pkgInfo.requestedPermissionsFlags; + + for (int indx = 0; indx < requestedPermissions.length; indx++) { + if ((requestedPermissionsFlags[indx] & PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { + try { + PermissionInfo pi = pm.getPermissionInfo(requestedPermissions[indx],0); + + if (pi.protectionLevel == PermissionInfo.PROTECTION_DANGEROUS) { + permissionsNotGranted.add(requestedPermissions[indx]); + if (DEBUG) { + Log.d(TAG, "Permission " + requestedPermissions[indx] + "not granted"); + } + } + } catch(PackageManager.NameNotFoundException e) { + //If package manager doesn't know of the permission we just continue since + //this permission won't end up in the list + } + } + } + return permissionsNotGranted.toArray(new String[permissionsNotGranted.size()]); + } + + /** + * Builds a ComponentName to identify the activity associated with the + * KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION category in the given package + * @param context + * @param packageName + * @return Returns the ComponentName or null if no activity was found. + */ + private static ComponentName getPermissionGranterComponentName(Context context, + String packageName) { + Intent rIntent = new Intent() + .setPackage(packageName) + .addCategory(KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION); + + List resolveInfo = context.getPackageManager(). + queryIntentActivities(rIntent, PackageManager.GET_RESOLVED_FILTER); + + if (resolveInfo.size() >= 1) { + if (DEBUG) { + if (resolveInfo.size() >= 2) { + Log.w(TAG, "Got " + resolveInfo.size() + " resolvers! Defaulting to " + + resolveInfo.get(0).activityInfo.name); + } + } + } + return (resolveInfo.size() >=1 ) ? + new ComponentName(packageName, resolveInfo.get(0).activityInfo.name) : + null; + } + + /** + * Builds an intent used to request the user to grant or revoke the supplied permissions. + * The intent will set the KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION + * category and an extra containing the list of permissions identified by + * KeyguardExternalView.EXTRA_PERMISSION_LIST + * @param context + * @param packageName + * @param permissionList + * @return Returns the intent if an activity associated with + * KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION category was found. Otherwise, it + * returns null + */ + public static Intent buildPermissionGrantRequestIntent(Context context, String packageName, + String[] permissionList) { + ComponentName componentName = getPermissionGranterComponentName(context, packageName); + if (componentName == null) return null; + + Intent permissionIntent = new Intent() + .setComponent(componentName) + .addCategory(KeyguardExternalView.CATEGORY_KEYGUARD_GRANT_PERMISSION) + .setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS|Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(KeyguardExternalView.EXTRA_PERMISSION_LIST, permissionList); + return permissionIntent; + } + + public static String getDefaultThemePackageName(Context context) { + final String defaultThemePkg = CMSettings.Secure.getString(context.getContentResolver(), + CMSettings.Secure.DEFAULT_THEME_PACKAGE); + if (!TextUtils.isEmpty(defaultThemePkg)) { + PackageManager pm = context.getPackageManager(); + try { + if (pm.getPackageInfo(defaultThemePkg, 0) != null) { + return defaultThemePkg; + } + } catch (PackageManager.NameNotFoundException e) { + // doesn't exist so system will be default + Log.w(TAG, "Default theme " + defaultThemePkg + " not found", e); + } + } + + return SYSTEM_DEFAULT; + } +} diff --git a/src/org/cyanogenmod/theme/util/WallpaperUtils.java b/src/org/cyanogenmod/theme/util/WallpaperUtils.java new file mode 100644 index 0000000..c07b99b --- /dev/null +++ b/src/org/cyanogenmod/theme/util/WallpaperUtils.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.util; + +import android.app.WallpaperManager; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Log; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; + +public class WallpaperUtils { + private static final String TAG = WallpaperUtils.class.getSimpleName(); + private static final int DEFAULT_COMPRESS_QUALITY = 90; + + /** + * createThumbnail from WallpaperCropActivity in f/b/packages/WallpaperCropper, renamed + * tp createPreview. + */ + public static Bitmap createPreview(Point size, Context context, Uri uri, byte[] imageBytes, + Resources res, int resId, int rotation, boolean leftAligned) { + int width = size.x; + int height = size.y; + + BitmapCropTask cropTask; + if (uri != null) { + cropTask = new BitmapCropTask( + context, uri, null, rotation, width, height, false, true, null); + } else if (imageBytes != null) { + cropTask = new BitmapCropTask( + imageBytes, null, rotation, width, height, false, true, null); + } else { + cropTask = new BitmapCropTask( + context, res, resId, null, rotation, width, height, false, true, null); + } + Point bounds = cropTask.getImageBounds(); + if (bounds == null || bounds.x == 0 || bounds.y == 0) { + return null; + } + + Matrix rotateMatrix = new Matrix(); + rotateMatrix.setRotate(rotation); + float[] rotatedBounds = new float[] { bounds.x, bounds.y }; + rotateMatrix.mapPoints(rotatedBounds); + rotatedBounds[0] = Math.abs(rotatedBounds[0]); + rotatedBounds[1] = Math.abs(rotatedBounds[1]); + + RectF cropRect = getMaxCropRect( + (int) rotatedBounds[0], (int) rotatedBounds[1], width, height, leftAligned); + cropTask.setCropBounds(cropRect); + + if (cropTask.cropBitmap()) { + return cropTask.getCroppedBitmap(); + } else { + return null; + } + } + + /** + * getMaxCropRect from WallpaperCropActivity in f/b/packages/WallpaperCropper + */ + protected static RectF getMaxCropRect( + int inWidth, int inHeight, int outWidth, int outHeight, boolean leftAligned) { + RectF cropRect = new RectF(); + // Get a crop rect that will fit this + if (inWidth / (float) inHeight > outWidth / (float) outHeight) { + cropRect.top = 0; + cropRect.bottom = inHeight; + cropRect.left = (inWidth - (outWidth / (float) outHeight) * inHeight) / 2; + cropRect.right = inWidth - cropRect.left; + if (leftAligned) { + cropRect.right -= cropRect.left; + cropRect.left = 0; + } + } else { + cropRect.left = 0; + cropRect.right = inWidth; + cropRect.top = (inHeight - (outHeight / (float) outWidth) * inWidth) / 2; + cropRect.bottom = inHeight - cropRect.top; + } + return cropRect; + } + + /** + * convertExtensionToCompressFormat from WallpaperCropActivity in f/b/packages/WallpaperCropper + */ + protected static Bitmap.CompressFormat convertExtensionToCompressFormat(String extension) { + return extension.equals("png") ? Bitmap.CompressFormat.PNG : Bitmap.CompressFormat.JPEG; + } + + /** + * getFileExtension from WallpaperCropActivity in f/b/packages/WallpaperCropper + */ + protected static String getFileExtension(String requestFormat) { + String outputFormat = (requestFormat == null) + ? "jpg" + : requestFormat; + outputFormat = outputFormat.toLowerCase(); + return (outputFormat.equals("png") || outputFormat.equals("gif")) + ? "png" // We don't support gif compression. + : "jpg"; + } + + /** + * BitmapCropTask from WallpaperCropActivity in f/b/packages/WallpaperCropper + */ + protected static class BitmapCropTask extends AsyncTask { + Uri mInUri = null; + Context mContext; + String mInFilePath; + byte[] mInImageBytes; + int mInResId = 0; + InputStream mInStream; + RectF mCropBounds = null; + int mOutWidth, mOutHeight; + int mRotation; + String mOutputFormat = "jpg"; // for now + boolean mSetWallpaper; + boolean mSaveCroppedBitmap; + Bitmap mCroppedBitmap; + Runnable mOnEndRunnable; + Resources mResources; + OnBitmapCroppedHandler mOnBitmapCroppedHandler; + boolean mNoCrop; + boolean mImageFromAsset; + + public BitmapCropTask(byte[] imageBytes, + RectF cropBounds, int rotation, int outWidth, int outHeight, + boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { + mInImageBytes = imageBytes; + init(cropBounds, rotation, + outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); + } + + public BitmapCropTask(Context c, Uri inUri, + RectF cropBounds, int rotation, int outWidth, int outHeight, + boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { + mContext = c; + mInUri = inUri; + init(cropBounds, rotation, + outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); + } + + public BitmapCropTask(Context c, Resources res, int inResId, + RectF cropBounds, int rotation, int outWidth, int outHeight, + boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { + mContext = c; + mInResId = inResId; + mResources = res; + init(cropBounds, rotation, + outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable); + } + + private void init(RectF cropBounds, int rotation, int outWidth, int outHeight, + boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) { + mCropBounds = cropBounds; + mRotation = rotation; + mOutWidth = outWidth; + mOutHeight = outHeight; + mSetWallpaper = setWallpaper; + mSaveCroppedBitmap = saveCroppedBitmap; + mOnEndRunnable = onEndRunnable; + } + + // Helper to setup input stream + private void regenerateInputStream() { + if (mInUri == null && mInResId == 0 && mInFilePath == null && + mInImageBytes == null && !mImageFromAsset) { + Log.w(TAG, "cannot read original file, no input URI, resource ID, or " + + "image byte array given"); + } else { + Utils.closeQuiet(mInStream); + try { + if (mInUri != null) { + mInStream = new BufferedInputStream( + mContext.getContentResolver().openInputStream(mInUri)); + } else if (mInFilePath != null) { + mInStream = mContext.openFileInput(mInFilePath); + } else if (mInImageBytes != null) { + mInStream = new BufferedInputStream( + new ByteArrayInputStream(mInImageBytes)); + } else { + mInStream = new BufferedInputStream( + mResources.openRawResource(mInResId)); + } + } catch (FileNotFoundException e) { + Log.w(TAG, "cannot read file: " + mInUri.toString(), e); + } + } + } + + public Point getImageBounds() { + regenerateInputStream(); + if (mInStream != null) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeStream(mInStream, null, options); + if (options.outWidth != 0 && options.outHeight != 0) { + return new Point(options.outWidth, options.outHeight); + } + } + return null; + } + + public void setCropBounds(RectF cropBounds) { + mCropBounds = cropBounds; + } + + public Bitmap getCroppedBitmap() { + return mCroppedBitmap; + } + public boolean cropBitmap() { + boolean failure = false; + + regenerateInputStream(); + + WallpaperManager wallpaperManager = null; + if (mSetWallpaper) { + wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); + } + if (mSetWallpaper && mNoCrop && mInStream != null) { + try { + wallpaperManager.setStream(mInStream); + } catch (IOException e) { + Log.w(TAG, "cannot write stream to wallpaper", e); + failure = true; + } + return !failure; + } + if (mInStream != null) { + // Find crop bounds (scaled to original image size) + Rect roundedTrueCrop = new Rect(); + Matrix rotateMatrix = new Matrix(); + Matrix inverseRotateMatrix = new Matrix(); + if (mRotation > 0) { + rotateMatrix.setRotate(mRotation); + inverseRotateMatrix.setRotate(-mRotation); + + mCropBounds.roundOut(roundedTrueCrop); + mCropBounds = new RectF(roundedTrueCrop); + + Point bounds = getImageBounds(); + + float[] rotatedBounds = new float[] { bounds.x, bounds.y }; + rotateMatrix.mapPoints(rotatedBounds); + rotatedBounds[0] = Math.abs(rotatedBounds[0]); + rotatedBounds[1] = Math.abs(rotatedBounds[1]); + + mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2); + inverseRotateMatrix.mapRect(mCropBounds); + mCropBounds.offset(bounds.x/2, bounds.y/2); + + regenerateInputStream(); + } + + mCropBounds.roundOut(roundedTrueCrop); + + if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) { + Log.w(TAG, "crop has bad values for full size image"); + failure = true; + return false; + } + + // See how much we're reducing the size of the image + int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth, + roundedTrueCrop.height() / mOutHeight); + + // Attempt to open a region decoder + BitmapRegionDecoder decoder = null; + try { + decoder = BitmapRegionDecoder.newInstance(mInStream, true); + } catch (IOException e) { + Log.w(TAG, "cannot open region decoder for file: " + mInUri.toString(), e); + } + + Bitmap crop = null; + if (decoder != null) { + // Do region decoding to get crop bitmap + BitmapFactory.Options options = new BitmapFactory.Options(); + if (scaleDownSampleSize > 1) { + options.inSampleSize = scaleDownSampleSize; + } + crop = decoder.decodeRegion(roundedTrueCrop, options); + decoder.recycle(); + } + + if (crop == null) { + // BitmapRegionDecoder has failed, try to crop in-memory + regenerateInputStream(); + Bitmap fullSize = null; + if (mInStream != null) { + BitmapFactory.Options options = new BitmapFactory.Options(); + if (scaleDownSampleSize > 1) { + options.inSampleSize = scaleDownSampleSize; + } + fullSize = BitmapFactory.decodeStream(mInStream, null, options); + } + if (fullSize != null) { + mCropBounds.left /= scaleDownSampleSize; + mCropBounds.top /= scaleDownSampleSize; + mCropBounds.bottom /= scaleDownSampleSize; + mCropBounds.right /= scaleDownSampleSize; + mCropBounds.roundOut(roundedTrueCrop); + + crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left, + roundedTrueCrop.top, roundedTrueCrop.width(), + roundedTrueCrop.height()); + } + } + + if (crop == null) { + Log.w(TAG, "cannot decode file: " + mInUri.toString()); + failure = true; + return false; + } + if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) { + float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() }; + rotateMatrix.mapPoints(dimsAfter); + dimsAfter[0] = Math.abs(dimsAfter[0]); + dimsAfter[1] = Math.abs(dimsAfter[1]); + + if (!(mOutWidth > 0 && mOutHeight > 0)) { + mOutWidth = Math.round(dimsAfter[0]); + mOutHeight = Math.round(dimsAfter[1]); + } + + RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]); + RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight); + + Matrix m = new Matrix(); + if (mRotation == 0) { + m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); + } else { + Matrix m1 = new Matrix(); + m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f); + Matrix m2 = new Matrix(); + m2.setRotate(mRotation); + Matrix m3 = new Matrix(); + m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f); + Matrix m4 = new Matrix(); + m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL); + + Matrix c1 = new Matrix(); + c1.setConcat(m2, m1); + Matrix c2 = new Matrix(); + c2.setConcat(m4, m3); + m.setConcat(c2, c1); + } + + Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(), + (int) returnRect.height(), Bitmap.Config.ARGB_8888); + if (tmp != null) { + Canvas c = new Canvas(tmp); + Paint p = new Paint(); + p.setFilterBitmap(true); + c.drawBitmap(crop, m, p); + crop = tmp; + } + } + + if (mSaveCroppedBitmap) { + mCroppedBitmap = crop; + } + + // Get output compression format + Bitmap.CompressFormat cf = + convertExtensionToCompressFormat(getFileExtension(mOutputFormat)); + + // Compress to byte array + ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048); + if (crop.compress(cf, DEFAULT_COMPRESS_QUALITY, tmpOut)) { + // If we need to set to the wallpaper, set it + if (mSetWallpaper && wallpaperManager != null) { + try { + byte[] outByteArray = tmpOut.toByteArray(); + wallpaperManager.setStream(new ByteArrayInputStream(outByteArray)); + if (mOnBitmapCroppedHandler != null) { + mOnBitmapCroppedHandler.onBitmapCropped(outByteArray); + } + } catch (IOException e) { + Log.w(TAG, "cannot write stream to wallpaper", e); + failure = true; + } + } + } else { + Log.w(TAG, "cannot compress bitmap"); + failure = true; + } + } + return !failure; // True if any of the operations failed + } + + @Override + protected Boolean doInBackground(Void... params) { + return cropBitmap(); + } + + @Override + protected void onPostExecute(Boolean result) { + if (mOnEndRunnable != null) { + mOnEndRunnable.run(); + } + } + + public interface OnBitmapCroppedHandler { + public void onBitmapCropped(byte[] imageBytes); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java b/src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java new file mode 100644 index 0000000..29534f7 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/AutoSnapHorizontalScrollView.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.HorizontalScrollView; +import android.widget.LinearLayout; + +public class AutoSnapHorizontalScrollView extends HorizontalScrollView { + private static final int SNAP_ON_UP_DELAY = 250; + + private int mScrollPositionOnUp; + + enum EventStates { + SCROLLING, + FLING + } + + private EventStates mSystemState = EventStates.SCROLLING; + + public AutoSnapHorizontalScrollView(Context context) { + super(context); + } + + public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AutoSnapHorizontalScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + Runnable mSnapRunnable = new Runnable(){ + @Override + public void run() { + snapItems(); + mSystemState = EventStates.SCROLLING; + } + }; + + /** + * Added runnable for snapping on an item when the user lifts up their finger and + * there is no scrolling taking place (i.e. no flinging) + */ + Runnable mSnapOnUpRunnable = new Runnable(){ + @Override + public void run() { + int scrollX = getScrollX(); + if (scrollX != mScrollPositionOnUp) { + mScrollPositionOnUp = scrollX; + postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); + } else { + snapItems(); + mSystemState = EventStates.SCROLLING; + } + } + }; + + @Override + public boolean onTouchEvent(MotionEvent ev) { + int action = ev.getAction(); + if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { + mSystemState = EventStates.FLING; + removeCallbacks(mSnapRunnable); + mScrollPositionOnUp = getScrollX(); + postDelayed(mSnapOnUpRunnable, SNAP_ON_UP_DELAY); + } else if (action == MotionEvent.ACTION_DOWN) { + mSystemState = EventStates.SCROLLING; + removeCallbacks(mSnapRunnable); + removeCallbacks(mSnapOnUpRunnable); + } + return super.onTouchEvent(ev); + } + + private void snapItems() { + Rect parentBounds = new Rect(); + getDrawingRect(parentBounds); + Rect childBounds = new Rect(); + ViewGroup parent = (ViewGroup) getChildAt(0); + for (int i = 0; i < parent.getChildCount(); i++) { + View view = parent.getChildAt(i); + view.getHitRect(childBounds); + if (childBounds.right >= parentBounds.left && childBounds.left <= parentBounds.left) { + // First partially visible child + if ((childBounds.right - parentBounds.left) >= + (parentBounds.left - childBounds.left)) { + smoothScrollTo(Math.abs(childBounds.left), 0); + } else { + /** + * Added code to take into account dividers so that we do not see + * one on the edge of the screen when items snap in place. + */ + int dividerWidth = 0; + if (parent instanceof LinearLayout) { + dividerWidth = ((LinearLayout) parent).getDividerWidth(); + } + smoothScrollTo(Math.abs(childBounds.right) + dividerWidth, 0); + } + break; + } + } + } + + // Overwrite measureChildX as we want our child to be able to tell the + // parents width but do not impose any limits (AT_MOST; AT_MAX) + @Override + protected void measureChild(View child, int parentWidthMeasureSpec, + int parentHeightMeasureSpec) { + ViewGroup.LayoutParams lp = child.getLayoutParams(); + + int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + + mPaddingBottom, lp.height); + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(parentWidthMeasureSpec) - (mPaddingLeft + mPaddingRight), + MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override + protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, + int parentHeightMeasureSpec, int heightUsed) { + MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); + + int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, + mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height); + + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( + MeasureSpec.getSize(parentWidthMeasureSpec) - + (mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + + widthUsed), MeasureSpec.UNSPECIFIED); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + if (mSystemState == EventStates.SCROLLING) { + return; + } + if (Math.abs(l - oldl) <= 1 && mSystemState == EventStates.FLING) { + removeCallbacks(mSnapRunnable); + postDelayed(mSnapRunnable, 100); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/BootAniImageView.java b/src/org/cyanogenmod/theme/widget/BootAniImageView.java new file mode 100644 index 0000000..b3f58a6 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/BootAniImageView.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.util.Log; +import android.widget.ImageView; + +import java.io.IOException; +import java.util.List; +import java.util.zip.ZipFile; + +import libcore.io.IoUtils; + +import org.cyanogenmod.theme.util.BootAnimationHelper; + +public class BootAniImageView extends ImageView { + private static final String TAG = BootAniImageView.class.getName(); + + private static final boolean DEBUG = false; + + private static final int MAX_BUFFERS = 2; + + private Bitmap[] mBuffers = new Bitmap[MAX_BUFFERS]; + private int mReadBufferIndex = 0; + private int mWriteBufferIndex = 0; + private ZipFile mBootAniZip; + + private List mAnimationParts; + private int mCurrentPart; + private int mCurrentFrame; + private int mCurrentPartPlayCount; + private int mFrameDuration; + + private boolean mActive = false; + + public BootAniImageView(Context context) { + this(context, null); + } + + public BootAniImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BootAniImageView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + if (visibility == VISIBLE) { + if (mBootAniZip != null) start(); + } else { + stop(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + // In case we end up in the mid dle of onDraw while the buffers are being recycled + // we catch the exception and just let frame not be rendered. + try { + super.onDraw(canvas); + } catch (RuntimeException e) { + Log.e(TAG, "Unable to draw boot animation frame."); + } + } + + public synchronized boolean setBootAnimation(ZipFile bootAni) { + if (bootAni == null) { + Log.w(TAG, "Boot animation ZipFile is null."); + return false; + } + // make sure we are stopped first + stop(); + + if (mBootAniZip != null) { + IoUtils.closeQuietly(mBootAniZip); + // This boot animation may be a different size than the previous + // one so clear out the buffers so they can be recreated with + // the correct size. + for (int i = 0; i < MAX_BUFFERS; i++) { + if (mBuffers[i] != null) { + mBuffers[i].recycle(); + mBuffers[i] = null; + } + } + } + mBootAniZip = bootAni; + + try { + mAnimationParts = BootAnimationHelper.parseAnimation(mBootAniZip); + } catch (Exception e) { + // "Gotta catch 'em all" + Log.e(TAG, "Unable to set boot animation", e); + mAnimationParts = null; + mBootAniZip = null; + return false; + } + + if (mAnimationParts == null || mAnimationParts.size() == 0) { + return false; + } + + final BootAnimationHelper.AnimationPart part = mAnimationParts.get(0); + mCurrentPart = 0; + mCurrentPartPlayCount = part.playCount; + mFrameDuration = part.frameRateMillis; + mWriteBufferIndex = mReadBufferIndex = 0; + mCurrentFrame = 0; + + getNextFrame(); + + return true; + } + + private void getNextFrame() { + if (mAnimationParts == null || mAnimationParts.size() == 0) return; + + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inBitmap = mBuffers[mWriteBufferIndex]; + opts.inPreferredConfig = Bitmap.Config.RGB_565; + opts.inMutable = true; + final BootAnimationHelper.AnimationPart part = mAnimationParts.get(mCurrentPart); + try { + mBuffers[mWriteBufferIndex] = + BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( + part.frames.get(mCurrentFrame++))), null, opts); + } catch (IllegalArgumentException iae) { + // In case we're here because the bitmap could not be re-used, try creating a new one + opts.inBitmap = null; + try { + if (DEBUG) { + Log.d(TAG, "Trying to load frame without reusing existing bitmap", iae); + } + if (mBuffers[mWriteBufferIndex] != null) { + // clean up our old bitmap + mBuffers[mWriteBufferIndex].recycle(); + mBuffers[mWriteBufferIndex] = null; + } + mBuffers[mWriteBufferIndex] = + BitmapFactory.decodeStream(mBootAniZip.getInputStream(mBootAniZip.getEntry( + part.frames.get(mCurrentFrame++))), null, opts); + } catch (Exception e) { + // Still failling? Let's log it and carry on. + Log.w(TAG, "Unable to get next frame", e); + } + } catch (IOException e) { + Log.w(TAG, "Unable to get next frame", e); + } + mWriteBufferIndex = (mWriteBufferIndex + 1) % MAX_BUFFERS; + if (mCurrentFrame >= part.frames.size()) { + if (mCurrentPartPlayCount > 0) { + if (--mCurrentPartPlayCount == 0) { + mCurrentPart++; + if (mCurrentPart >= mAnimationParts.size()) mCurrentPart = 0; + mCurrentFrame = 0; + mCurrentPartPlayCount = mAnimationParts.get(mCurrentPart).playCount; + } else { + mCurrentFrame = 0; + } + } else { + mCurrentFrame = 0; + } + } + } + + public void start() { + if (mAnimationParts == null) return; + + mActive = true; + post(mUpdateAnimationRunnable); + } + + public void stop() { + mActive = false; + removeCallbacks(mUpdateImageRunnable); + removeCallbacks(mUpdateAnimationRunnable); + } + + private Runnable mUpdateAnimationRunnable = new Runnable() { + @Override + public void run() { + if (!mActive) return; + BootAniImageView.this.postDelayed(mUpdateAnimationRunnable, mFrameDuration); + if (!isOffScreen()) { + BootAniImageView.this.post(mUpdateImageRunnable); + mReadBufferIndex = (mReadBufferIndex + 1) % MAX_BUFFERS; + getNextFrame(); + } + } + }; + + private Runnable mUpdateImageRunnable = new Runnable() { + @Override + public void run() { + setImageBitmap(mBuffers[mReadBufferIndex]); + } + }; + + private boolean isOffScreen() { + int[] pos = new int[2]; + getLocationOnScreen(pos); + return pos[1] >= mContext.getResources().getDisplayMetrics().heightPixels; + } +} diff --git a/src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java b/src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java new file mode 100644 index 0000000..e00e67a --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/ConfirmCancelOverlay.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.cyanogenmod.theme.chooser.R; + +public class ConfirmCancelOverlay extends FrameLayout { + + private View mAcceptButton; + private View mCancelButton; + private TextView mTitle; + + private OnOverlayDismissedListener mListener; + + public ConfirmCancelOverlay(Context context) { + this(context, null); + } + + public ConfirmCancelOverlay(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ConfirmCancelOverlay(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mAcceptButton = findViewById(R.id.accept); + mCancelButton = findViewById(R.id.cancel); + mTitle = (TextView) findViewById(R.id.overlay_title); + + mAcceptButton.setOnClickListener(mClickListener); + mCancelButton.setOnClickListener(mClickListener); + } + + public void setTitle(CharSequence title) { + mTitle.setText(title); + } + + public void setTitle(int resId) { + mTitle.setText(resId); + } + + public void setOnOverlayDismissedListener(OnOverlayDismissedListener listener) { + mListener = listener; + } + + public void dismiss() { + if (mListener != null) { + mListener.onDismissed(false); + } + } + + private OnClickListener mClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + if (mListener != null) { + mListener.onDismissed(v == mAcceptButton); + } + } + }; + + public interface OnOverlayDismissedListener { + public void onDismissed(boolean accepted); + } +} diff --git a/src/org/cyanogenmod/theme/widget/FittedTextView.java b/src/org/cyanogenmod/theme/widget/FittedTextView.java new file mode 100644 index 0000000..2451d25 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/FittedTextView.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.graphics.Paint; +import android.text.method.TransformationMethod; +import android.text.method.AllCapsTransformationMethod; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.widget.TextView; + +/** + * Change the font size to match the measured + * textview size by width + * + */ +public class FittedTextView extends TextView { + private Paint mPaint; + //If set to true, the text will be resized to fit the view. + private boolean mAutoFitText = true; + //Used to instruct whether the text should be expanded to fill out the view, even if the text + //fits without being resized + private boolean mAutoExpand = true; + + public FittedTextView(Context context) { + this(context, null); + } + + public FittedTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public FittedTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mPaint = new Paint(); + } + + protected void setAutoFitText(boolean autoFit) { + mAutoFitText = autoFit; + } + + protected boolean getAutoFitText() { + return mAutoFitText; + } + + protected void setAutoExpand(boolean autoExpand) { + mAutoExpand = autoExpand; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + if (!mAutoFitText) return; + + final float THRESHOLD = 0.5f; + final float TARGET_WIDTH = getMeasuredWidth(); + String text = getText().toString(); + TransformationMethod tm = getTransformationMethod(); + if (tm != null && tm instanceof AllCapsTransformationMethod) { + text = getText().toString().toUpperCase(); + } + mPaint.set(getPaint()); + + if (mPaint.measureText(text) <= TARGET_WIDTH && !mAutoExpand) return; + + float max = 200; + float min = 2; + while(max > min) { + float size = (max+min) / 2; + mPaint.setTextSize(size); + float measuredWidth = mPaint.measureText(text); + if (Math.abs(TARGET_WIDTH - measuredWidth) <= THRESHOLD) { + break; + } else if (measuredWidth > TARGET_WIDTH) { + max = size-1; + } else { + min = size+1; + } + } + this.setTextSize(TypedValue.COMPLEX_UNIT_PX, min-1); + } +} diff --git a/src/org/cyanogenmod/theme/widget/LatoTextView.java b/src/org/cyanogenmod/theme/widget/LatoTextView.java new file mode 100644 index 0000000..0cb02ae --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/LatoTextView.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Typeface; +import android.util.AttributeSet; + +import org.cyanogenmod.theme.chooser.R; +import org.cyanogenmod.theme.util.Utils; + +import java.io.File; + +/** + * A custom TextView that always uses the Lato font + */ +public class LatoTextView extends FittedTextView { + private static final int NUM_TYPEFACE_PER_FAMILY = 4; + + private static final String FONT_ASSSET_DIR = "fonts"; + // Regular fonts + private static final String LATO_REGULAR_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-Regular.ttf"; + private static final String LATO_REGULAR_BOLD_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-RegBold.ttf"; + private static final String LATO_REGULAR_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-RegItalic.otf"; + private static final String LATO_REGULAR_BOLD_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-RegBoldItalic.ttf"; + // Condensed fonts + private static final String LATO_CONDENSED_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-Cond.ttf"; + private static final String LATO_CONDENSED_BOLD_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-CondBold.ttf"; + private static final String LATO_CONDENSED_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-CondItalic.ttf"; + private static final String LATO_CONDENSED_BOLD_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-CondBoldItalic.ttf"; + // Light fonts + private static final String LATO_LIGHT_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-Light.ttf"; + private static final String LATO_LIGHT_ITALIC_PATH = + FONT_ASSSET_DIR + File.separator + "Lato-LightItalic.ttf"; + + private static final String CONDENSED = "condensed"; + private static final String LIGHT = "light"; + + private static Typeface[] sLatoRegularTypeface; + private static Typeface[] sLatoCondensedTypeface; + private static Typeface[] sLatoLightTypeface; + private static final Object sLock = new Object(); + + // Retrieving these attributes is done via reflection so let's just do this once and share + // it amongst the other instances of LatoTextView + private static int[] sTextViewStyleAttributes; + + public LatoTextView(Context context) { + this(context, null); + } + + public LatoTextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LatoTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + synchronized (sLock) { + AssetManager assets = context.getAssets(); + if (sLatoRegularTypeface == null) { + sLatoRegularTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; + sLatoRegularTypeface[Typeface.NORMAL] = + Typeface.createFromAsset(assets, LATO_REGULAR_PATH); + sLatoRegularTypeface[Typeface.BOLD] = + Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_PATH); + sLatoRegularTypeface[Typeface.ITALIC] = + Typeface.createFromAsset(assets, LATO_REGULAR_ITALIC_PATH); + sLatoRegularTypeface[Typeface.BOLD_ITALIC] = + Typeface.createFromAsset(assets, LATO_REGULAR_BOLD_ITALIC_PATH); + } + if (sLatoCondensedTypeface == null) { + sLatoCondensedTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; + sLatoCondensedTypeface[Typeface.NORMAL] = + Typeface.createFromAsset(assets, LATO_CONDENSED_PATH); + sLatoCondensedTypeface[Typeface.BOLD] = + Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_PATH); + sLatoCondensedTypeface[Typeface.ITALIC] = + Typeface.createFromAsset(assets, LATO_CONDENSED_ITALIC_PATH); + sLatoCondensedTypeface[Typeface.BOLD_ITALIC] = + Typeface.createFromAsset(assets, LATO_CONDENSED_BOLD_ITALIC_PATH); + } + if (sLatoLightTypeface == null) { + sLatoLightTypeface = new Typeface[NUM_TYPEFACE_PER_FAMILY]; + sLatoLightTypeface[Typeface.NORMAL] = + Typeface.createFromAsset(assets, LATO_LIGHT_PATH); + sLatoLightTypeface[Typeface.BOLD] = + sLatoRegularTypeface[Typeface.BOLD]; + sLatoLightTypeface[Typeface.ITALIC] = + Typeface.createFromAsset(assets, LATO_LIGHT_ITALIC_PATH); + sLatoLightTypeface[Typeface.BOLD_ITALIC] = + sLatoRegularTypeface[Typeface.BOLD_ITALIC]; + } + } + + final Resources.Theme theme = context.getTheme(); + if (sTextViewStyleAttributes == null) { + sTextViewStyleAttributes = + Utils.getResourceDeclareStyleableIntArray("com.android.internal", "TextView"); + } + + if (sTextViewStyleAttributes != null) { + TypedArray a = + theme.obtainStyledAttributes(attrs, sTextViewStyleAttributes, defStyle, 0); + String fontFamily = "sans-serif"; + int styleIndex = Typeface.NORMAL; + if (a != null) { + int n = a.getIndexCount(); + for (int i = 0; i < n; i++) { + int attr = a.getIndex(i); + + final Resources res = getResources(); + int attrFontFamily = + res.getIdentifier("TextView_fontFamily", "styleable", "android"); + int attrTextStyle = + res.getIdentifier("TextView_textStyle", "styleable", "android"); + if (attr == attrFontFamily) { + fontFamily = a.getString(attr); + } else if (attr == attrTextStyle) { + styleIndex = a.getInt(attr, styleIndex); + } + } + a.recycle(); + } + + setTypefaceFromAttrs(fontFamily, styleIndex); + setAutoExpand(false); + TypedArray styledAttrs = context.obtainStyledAttributes(attrs, + R.styleable.FittedTextView, 0, 0); + try { + //Although we extend FittedTextView, we don't want all instances to auto fit the + //text, so we check if autoFitText has been set in the attributes. Default to false + boolean fit = styledAttrs.getBoolean(R.styleable.FittedTextView_autoFitText, false); + setAutoFitText(fit); + } finally { + styledAttrs.recycle(); + } + } + } + + private void setTypefaceFromAttrs(String familyName, int styleIndex) { + Typeface tf = null; + if (familyName != null) { + Typeface[] typefaces = sLatoRegularTypeface; + if (familyName.contains(CONDENSED)) { + typefaces = sLatoCondensedTypeface; + } else if (familyName.contains(LIGHT)) { + typefaces = sLatoLightTypeface; + } + tf = typefaces[styleIndex]; + if (tf != null) { + setTypeface(tf); + return; + } + } + setTypeface(tf, styleIndex); + } +} diff --git a/src/org/cyanogenmod/theme/widget/LockableScrollView.java b/src/org/cyanogenmod/theme/widget/LockableScrollView.java new file mode 100644 index 0000000..b4c9ffc --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/LockableScrollView.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.ScrollView; + +public class LockableScrollView extends ScrollView { + private boolean mScrollingEnabled = true; + + public LockableScrollView(Context context) { + this(context, null); + } + + public LockableScrollView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public LockableScrollView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setScrollingEnabled(boolean enabled) { + mScrollingEnabled = enabled; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return mScrollingEnabled && super.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + return mScrollingEnabled && super.onTouchEvent(ev); + default: + return super.onTouchEvent(ev); + } + } + + @Override + public void setOverScrollMode(int mode) { + // Some themes can cause theme chooser to crash when creating the EdgeEffects for + // the scroll view. If an exception occurs we fallback to no overscroll + try { + super.setOverScrollMode(mode); + } catch (Exception e) { + super.setOverScrollMode(OVER_SCROLL_NEVER); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/NavBarSpace.java b/src/org/cyanogenmod/theme/widget/NavBarSpace.java new file mode 100644 index 0000000..720df29 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/NavBarSpace.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import org.cyanogenmod.theme.util.Utils; + +/** + * A simple view used to pad layouts so that content floats above the + * navigation bar. This is best used with transparent or translucent + * navigation bars where the content can go behind them. + */ +public class NavBarSpace extends View { + + public NavBarSpace(Context context) { + this(context, null); + } + + public NavBarSpace(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public NavBarSpace(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + if (!Utils.hasNavigationBar(getContext())) { + this.setVisibility(View.GONE); + } else { + this.setVisibility(View.VISIBLE); + } + } +} diff --git a/src/org/cyanogenmod/theme/widget/ThemeTagLayout.java b/src/org/cyanogenmod/theme/widget/ThemeTagLayout.java new file mode 100644 index 0000000..18e3da2 --- /dev/null +++ b/src/org/cyanogenmod/theme/widget/ThemeTagLayout.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2016 Cyanogen, Inc. + * Copyright (C) 2016 The CyanogenMod 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 org.cyanogenmod.theme.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.cyanogenmod.theme.chooser.R; + +public class ThemeTagLayout extends LinearLayout { + private ImageView mAppliedTag; + private TextView mCustomizedTag; + private TextView mUpdatedTag; + private TextView mDefaultTag; + private TextView mLegacyTag; + + public ThemeTagLayout(Context context) { + this(context, null); + } + + public ThemeTagLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ThemeTagLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + LayoutInflater inflater = + (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mAppliedTag = (ImageView) inflater.inflate(R.layout.tag_applied, this, false); + mCustomizedTag = (TextView) inflater.inflate(R.layout.tag_customized, this, false); + mUpdatedTag = (TextView) inflater.inflate(R.layout.tag_updated, this, false); + mDefaultTag = (TextView) inflater.inflate(R.layout.tag_default, this, false); + mLegacyTag = (TextView) inflater.inflate(R.layout.tag_legacy, this, false); + } + + public void setAppliedTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_applied) != null) return; + addView(mAppliedTag, 0); + } else { + if (findViewById(R.id.tag_applied) == null) return; + removeView(mAppliedTag); + } + } + + public void setCustomizedTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_customized) != null) return; + final int childCount = getChildCount(); + if (childCount > 1) { + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child != mAppliedTag) { + addView(mCustomizedTag, i); + break; + } + } + } else { + addView(mCustomizedTag); + } + } else { + if (findViewById(R.id.tag_customized) == null) return; + removeView(mCustomizedTag); + } + } + + public boolean isCustomizedTagEnabled() { + return findViewById(R.id.tag_customized) != null; + } + + public void setUpdatedTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_updated) != null) return; + final int childCount = getChildCount(); + if (childCount > 2) { + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child != mAppliedTag && child != mCustomizedTag) { + addView(mUpdatedTag, i); + break; + } + } + } else { + addView(mUpdatedTag); + } + } else { + if (findViewById(R.id.tag_updated) == null) return; + removeView(mUpdatedTag); + } + } + + public boolean isUpdatedTagEnabled() { + return findViewById(R.id.tag_updated) != null; + } + + public void setDefaultTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_default) != null) return; + addView(mDefaultTag); + } else { + if (findViewById(R.id.tag_default) == null) return; + removeView(mDefaultTag); + } + } + + public void setLegacyTagEnabled(boolean enabled) { + if (enabled) { + if (findViewById(R.id.tag_legacy) != null) return; + addView(mLegacyTag); + } else { + if (findViewById(R.id.tag_legacy) == null) return; + removeView(mLegacyTag); + } + } +} -- cgit v1.1