summaryrefslogtreecommitdiffstats
path: root/src/com/cyngn/theme
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/cyngn/theme')
-rw-r--r--src/com/cyngn/theme/chooser/AppReceiver.java58
-rw-r--r--src/com/cyngn/theme/chooser/ChooserActivity.java489
-rw-r--r--src/com/cyngn/theme/chooser/ComponentCardView.java202
-rw-r--r--src/com/cyngn/theme/chooser/ComponentSelector.java765
-rw-r--r--src/com/cyngn/theme/chooser/IconTransitionDrawable.java171
-rw-r--r--src/com/cyngn/theme/chooser/NotificationHijackingService.java87
-rw-r--r--src/com/cyngn/theme/chooser/PagerContainer.java226
-rw-r--r--src/com/cyngn/theme/chooser/ThemeFragment.java1902
-rw-r--r--src/com/cyngn/theme/chooser/WallpaperCardView.java115
-rw-r--r--src/com/cyngn/theme/util/AudioUtils.java137
-rw-r--r--src/com/cyngn/theme/util/BootAnimationHelper.java253
-rw-r--r--src/com/cyngn/theme/util/FontConfigParser.java173
-rw-r--r--src/com/cyngn/theme/util/IconPreviewHelper.java195
-rw-r--r--src/com/cyngn/theme/util/NotificationHelper.java77
-rw-r--r--src/com/cyngn/theme/util/ThemedTypefaceHelper.java136
-rw-r--r--src/com/cyngn/theme/util/TypefaceHelperCache.java55
-rw-r--r--src/com/cyngn/theme/util/Utils.java267
-rw-r--r--src/com/cyngn/theme/widget/BootAniImageView.java184
-rw-r--r--src/com/cyngn/theme/widget/ClickableViewPager.java74
-rw-r--r--src/com/cyngn/theme/widget/FittedTextView.java71
20 files changed, 5637 insertions, 0 deletions
diff --git a/src/com/cyngn/theme/chooser/AppReceiver.java b/src/com/cyngn/theme/chooser/AppReceiver.java
new file mode 100644
index 0000000..ef39669
--- /dev/null
+++ b/src/com/cyngn/theme/chooser/AppReceiver.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 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 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 com.cyngn.theme.util.NotificationHelper;
+
+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();
+ boolean isReplacing = intent.getExtras().getBoolean(Intent.EXTRA_REPLACING, false);
+
+ if (Intent.ACTION_PACKAGE_ADDED.equals(action) && !isReplacing) {
+ try {
+ if (isTheme(context, pkgName)) {
+ NotificationHelper.postThemeInstalledNotification(context, pkgName);
+ }
+ } catch (NameNotFoundException e) {
+ }
+ } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) {
+ NotificationHelper.cancelNotificationForPackage(context, pkgName);
+ }
+ }
+
+ private boolean isTheme(Context context, String pkgName) throws NameNotFoundException {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0);
+ if (pi == null) return false;
+
+ if ((pi.themeInfos != null && pi.themeInfos.length > 0) ||
+ (pi.legacyThemeInfos != null && pi.legacyThemeInfos.length > 0)) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/src/com/cyngn/theme/chooser/ChooserActivity.java b/src/com/cyngn/theme/chooser/ChooserActivity.java
new file mode 100644
index 0000000..13cdbcb
--- /dev/null
+++ b/src/com/cyngn/theme/chooser/ChooserActivity.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright (C) 2014 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 com.cyngn.theme.chooser;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.ThemeConfig;
+import android.content.res.ThemeManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.ThemesContract;
+import android.provider.ThemesContract.ThemesColumns;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.FragmentStatePagerAdapter;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v4.view.ThemeViewPager;
+import android.support.v4.view.ViewPager;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.Button;
+
+import com.cyngn.theme.util.TypefaceHelperCache;
+import com.cyngn.theme.util.Utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_ALARMS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_RINGTONES;
+
+public class ChooserActivity extends FragmentActivity
+ implements LoaderManager.LoaderCallbacks<Cursor>, ThemeManager.ThemeChangeListener {
+ public static final String DEFAULT = ThemeConfig.HOLO_DEFAULT;
+ public static final int REQUEST_UNINSTALL = 1; // Request code
+
+ private static final long SLIDE_CONTENT_ANIM_DURATION = 300L;
+ private static final long MOVE_TO_MY_THEME_DELAY = 750L;
+
+ private static final int OFFSCREEN_PAGE_LIMIT = 3;
+
+ private static final int LOADER_ID_INSTALLED_THEMES = 1000;
+ private static final int LOADER_ID_APPLIED = 1001;
+
+ private PagerContainer mContainer;
+ private ThemeViewPager mPager;
+
+ private ThemesAdapter mAdapter;
+ private ThemeManager mService;
+ private boolean mExpanded = false;
+ private ComponentSelector mSelector;
+ private View mSaveApplyLayout;
+ private int mContainerYOffset = 0;
+ private TypefaceHelperCache mTypefaceHelperCache;
+ private boolean mIsAnimating;
+ private Handler mHandler;
+
+ // Current system theme configuration as component -> pkgName
+ private Map<String, String> mCurrentTheme = new HashMap<String, String>();
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ mContainer = (PagerContainer) findViewById(R.id.pager_container);
+ mPager = (ThemeViewPager) findViewById(R.id.viewpager);
+
+ mPager.setOnClickListener(mPagerClickListener);
+ mAdapter = new ThemesAdapter(this);
+ 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);
+
+ mService = (ThemeManager) getSystemService(Context.THEME_SERVICE);
+ getSupportLoaderManager().restartLoader(LOADER_ID_APPLIED, null, this);
+
+ mSaveApplyLayout = findViewById(R.id.save_apply_layout);
+ if (!Utils.hasNavigationBar(this)) {
+ mSaveApplyLayout.findViewById(R.id.navbar_padding).setVisibility(View.GONE);
+ }
+ mSaveApplyLayout.findViewById(R.id.save_apply_button).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mIsAnimating) return;
+ hideSaveApplyButton();
+ mExpanded = false;
+ mContainer.setClickable(false);
+ final ThemeFragment f = getCurrentFragment();
+ f.fadeOutCards(new Runnable() {
+ public void run() {
+ mContainer.collapse();
+ f.collapse(true);
+ }
+ });
+ setAnimatingStateAndScheduleFinish();
+ }
+ });
+ mTypefaceHelperCache = TypefaceHelperCache.getInstance();
+ mHandler = new Handler();
+ }
+
+ public void hideSaveApplyButton() {
+ 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 setAnimatingStateAndScheduleFinish() {
+ mIsAnimating = true;
+ mContainer.setIsAnimating(true);
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ mIsAnimating = false;
+ mContainer.setIsAnimating(false);
+ }
+ }, ThemeFragment.ANIMATE_START_DELAY + ThemeFragment.ANIMATE_DURATION);
+ }
+
+ /**
+ * Disable the ViewPager while a theme change is occuring
+ */
+ public void themeChangeStarted() {
+ mPager.setEnabled(false);
+ }
+
+ /**
+ * Re-enable the ViewPager and update the "My theme" fragment if available
+ */
+ public void themeChangeEnded() {
+ if (mPager.getCurrentItem() != 0) {
+ ThemeFragment f;
+ if (mPager.getCurrentItem() <= OFFSCREEN_PAGE_LIMIT) {
+ // Clear the "My theme" card so it loads the newly applied changes
+ f = (ThemeFragment) mAdapter.instantiateItem(mPager, 0);
+ if (f != null) f.clearChanges();
+ }
+
+ // clear the current card so it returns to it's previous state
+ f = getCurrentFragment();
+ if (f != null) f.clearChanges();
+ mPager.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mPager.setCurrentItem(0, true);
+ }
+ }, MOVE_TO_MY_THEME_DELAY);
+ }
+ mPager.setEnabled(true);
+ }
+
+ public ComponentSelector getComponentSelector() {
+ return mSelector;
+ }
+
+ public void showComponentSelector(String component, 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) hideSaveApplyButton();
+ mSelector.show(component, itemsPerPage, height);
+
+ // determine if we need to shift the cards up
+ int[] coordinates = new int[2];
+ v.getLocationOnScreen(coordinates);
+ coordinates[1] += v.getHeight();
+ int top = getWindowManager().getDefaultDisplay().getHeight() - height;
+ if (coordinates[1] > top) {
+ slideContentUp(top - coordinates[1]);
+ }
+ }
+ }
+
+ private void slideContentUp(int yDelta) {
+ yDelta -= getResources().getDimensionPixelSize(R.dimen.content_offset_padding);
+ mContainerYOffset = yDelta;
+ mContainer.animate().translationYBy(yDelta).setDuration(SLIDE_CONTENT_ANIM_DURATION);
+ }
+
+ private void slideContentDown(final int yDelta) {
+ mContainer.animate().translationYBy(-yDelta).setDuration(SLIDE_CONTENT_ANIM_DURATION);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mService.onClientResumed(this);
+ getSupportLoaderManager().restartLoader(LOADER_ID_APPLIED, null, this);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (mSelector.isEnabled()) {
+ mSelector.hide();
+ if (mContainerYOffset != 0) {
+ slideContentDown(mContainerYOffset);
+ mContainerYOffset = 0;
+ }
+ final ThemeFragment f = getCurrentFragment();
+ f.fadeInCards();
+ } else if (mExpanded) {
+ if (mIsAnimating) {
+ return;
+ }
+
+ if (mSaveApplyLayout.getVisibility() == View.VISIBLE) {
+ hideSaveApplyButton();
+ getCurrentFragment().clearChanges();
+ }
+ mExpanded = false;
+ final ThemeFragment f = getCurrentFragment();
+ f.fadeOutCards(new Runnable() {
+ public void run() {
+ mContainer.collapse();
+ f.collapse(false);
+ }
+ });
+ setAnimatingStateAndScheduleFinish();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mService.onClientPaused(this);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mService.onClientDestroyed(this);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (mTypefaceHelperCache.getTypefaceCount() <= 0) {
+ new TypefacePreloadTask().execute();
+ }
+ }
+
+ private View.OnClickListener mPagerClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!mExpanded && !mIsAnimating) {
+ mExpanded = true;
+ mContainer.setClickable(false);
+ mContainer.expand();
+ ThemeFragment f = getCurrentFragment();
+ f.expand();
+ setAnimatingStateAndScheduleFinish();
+ }
+ }
+ };
+
+ private ComponentSelector.OnOpenCloseListener mOpenCloseListener = new ComponentSelector.OnOpenCloseListener() {
+ @Override
+ public void onSelectorOpened() {
+ }
+
+ @Override
+ public void onSelectorClosed() {
+ ThemeFragment f = getCurrentFragment();
+ if (f.componentsChanged()) {
+ mSaveApplyLayout.setVisibility(View.VISIBLE);
+ mSaveApplyLayout.startAnimation(AnimationUtils.loadAnimation(ChooserActivity.this,
+ R.anim.component_selection_animate_in));
+ }
+ }
+ };
+
+ 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.
+ return (ThemeFragment) mAdapter.instantiateItem(mPager, mPager.getCurrentItem());
+ }
+
+ private void populateCurrentTheme(Cursor c) {
+ c.moveToPosition(-1);
+ while(c.moveToNext()) {
+ int mixkeyIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_KEY);
+ int pkgIdx = c.getColumnIndex(ThemesContract.MixnMatchColumns.COL_VALUE);
+ String mixkey = c.getString(mixkeyIdx);
+ String component = ThemesContract.MixnMatchColumns.mixNMatchKeyToComponent(mixkey);
+ String pkg = c.getString(pkgIdx);
+ mCurrentTheme.put(component, pkg);
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+ 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.)
+ mAdapter.swapCursor(data);
+ mAdapter.notifyDataSetChanged();
+ break;
+ case LOADER_ID_APPLIED:
+ getSupportLoaderManager().restartLoader(LOADER_ID_INSTALLED_THEMES, null, this);
+ populateCurrentTheme(data);
+ break;
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ switch (loader.getId()) {
+ case LOADER_ID_INSTALLED_THEMES:
+ mAdapter.swapCursor(null);
+ mAdapter.notifyDataSetChanged();
+ break;
+ }
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ String selection = null;
+ String selectionArgs[] = null;
+ String sortOrder = null;
+ Uri contentUri = null;
+
+ switch (id) {
+ case LOADER_ID_INSTALLED_THEMES:
+ selection = ThemesColumns.PRESENT_AS_THEME + "=?";
+ selectionArgs = new String[] { "1" };
+ // sort in ascending order but make sure the "default" theme is always first
+ sortOrder = "(" + ThemesColumns.IS_DEFAULT_THEME + "=1) DESC, "
+ + ThemesColumns.TITLE + " ASC";
+ contentUri = ThemesColumns.CONTENT_URI;
+ 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(this, contentUri, null, selection,
+ selectionArgs, sortOrder);
+ }
+
+ @Override
+ public void onProgress(int progress) {
+
+ }
+
+ @Override
+ public void onFinish(boolean isSuccess) {
+
+ }
+
+ public class ThemesAdapter extends FragmentStatePagerAdapter {
+ private Cursor mCursor;
+ private Context mContext;
+
+ public ThemesAdapter(Context context) {
+ super(getSupportFragmentManager());
+ mContext = context;
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ String pkgName;
+ if (position == 0) {
+ pkgName = ThemeFragment.CURRENTLY_APPLIED_THEME;
+ } else {
+ mCursor.moveToPosition(position - 1);
+ int pkgIdx = mCursor.getColumnIndex(ThemesColumns.PKG_NAME);
+ pkgName = mCursor.getString(pkgIdx);
+ }
+ ThemeFragment f = ThemeFragment.newInstance(pkgName);
+ f.setCurrentTheme(mCurrentTheme);
+ return f;
+ }
+
+ @Override
+ public int getItemPosition(Object object) {
+ ThemeFragment fragment = (ThemeFragment) object;
+ if (fragment.isUninstalled()) {
+ return POSITION_NONE;
+ }
+ 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 mCursor == null ? 1 : mCursor.getCount() + 1;
+ }
+
+ public void swapCursor(Cursor c) {
+ mCursor = c;
+ }
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/src/com/cyngn/theme/chooser/ComponentCardView.java b/src/com/cyngn/theme/chooser/ComponentCardView.java
new file mode 100644
index 0000000..555fc7a
--- /dev/null
+++ b/src/com/cyngn/theme/chooser/ComponentCardView.java
@@ -0,0 +1,202 @@
+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.2f;
+ private static final int BACKGROUND_SEMI_OPAQUE_ALPHA = (int) (256.0f * SEMI_OPAQUE_ALPHA);
+
+ protected TextView mLabel;
+
+ // Expanded Padding
+ int mExpandPadLeft;
+ int mExpandPadTop;
+ int mExpandPadRight;
+ int mExpandPadBottom;
+
+ 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);
+ }
+
+ @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();
+ }
+
+ public void expand(boolean showLabel) {
+ TransitionDrawable bg = null;
+ if (getBackground() instanceof TransitionDrawable) {
+ bg = (TransitionDrawable) getBackground();
+ }
+ if (bg != null) {
+ Rect paddingRect = new Rect();
+ bg.getPadding(paddingRect);
+ }
+
+ setPadding(mExpandPadLeft, mExpandPadTop, 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() {
+ 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() {
+ if (mLabel != null) {
+ mLabel.animate().alpha(SEMI_OPAQUE_ALPHA).setDuration(CARD_FADE_DURATION);
+ }
+ final ValueAnimator bgAlphaAnimator = ValueAnimator.ofObject(new IntEvaluator(), 255,
+ BACKGROUND_SEMI_OPAQUE_ALPHA);
+ bgAlphaAnimator.setDuration(CARD_FADE_DURATION);
+ bgAlphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ getBackground().setAlpha((Integer) animation.getAnimatedValue());
+ }
+ });
+ bgAlphaAnimator.start();
+ }
+
+ /**
+ * Animates the card background and the title back to full opacity.
+ */
+ public void animateCardFadeIn() {
+ if (getBackground().getAlpha() > 51) return;
+ if (mLabel != null) {
+ mLabel.animate().alpha(1f).setDuration(CARD_FADE_DURATION);
+ }
+ final ValueAnimator bgAlphaAnimator = ValueAnimator.ofObject(new IntEvaluator(),
+ BACKGROUND_SEMI_OPAQUE_ALPHA, 255);
+ bgAlphaAnimator.setDuration(CARD_FADE_DURATION);
+ bgAlphaAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ getBackground().setAlpha((Integer) animation.getAnimatedValue());
+ }
+ });
+ bgAlphaAnimator.start();
+ }
+
+ /**
+ * 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 = (int) v.getX();
+ final int y = (int) v.getY();
+ 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();
+ }
+}
diff --git a/src/com/cyngn/theme/chooser/ComponentSelector.java b/src/com/cyngn/theme/chooser/ComponentSelector.java
new file mode 100644
index 0000000..a4dab26
--- /dev/null
+++ b/src/com/cyngn/theme/chooser/ComponentSelector.java
@@ -0,0 +1,765 @@
+/*
+ * Copyright (C) 2014 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 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.Cursor;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.media.MediaPlayer;
+import android.media.RingtoneManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.provider.ThemesContract;
+import android.provider.ThemesContract.MixnMatchColumns;
+import android.provider.ThemesContract.PreviewColumns;
+import android.provider.ThemesContract.ThemesColumns;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+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.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.viewpagerindicator.PageIndicator;
+import com.cyngn.theme.util.AudioUtils;
+import com.cyngn.theme.util.ThemedTypefaceHelper;
+import com.cyngn.theme.util.TypefaceHelperCache;
+import com.cyngn.theme.util.Utils;
+
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_ALARMS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_RINGTONES;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_ICONS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_FONTS;
+
+public class ComponentSelector extends LinearLayout
+ implements LoaderManager.LoaderCallbacks<Cursor> {
+ private static final String TAG = ComponentSelector.class.getSimpleName();
+
+ public static final boolean DEBUG_SELECTOR = false;
+
+ private static final int LOADER_ID_STATUS_BAR = 100;
+ private static final int LOADER_ID_NAVIGATION_BAR = 101;
+ private static final int LOADER_ID_FONT = 102;
+ private static final int LOADER_ID_ICON = 103;
+ private static final int LOADER_ID_STYLE = 104;
+ private static final int LOADER_ID_WALLPAPER = 105;
+ private static final int LOADER_ID_BOOTANIMATIONS = 106;
+ private static final int LOADER_ID_RINGTONE = 107;
+ private static final int LOADER_ID_NOTIFICATION = 108;
+ private static final int LOADER_ID_ALARM = 109;
+ private static final int LOADER_ID_LOCKSCREEN = 110;
+
+ private Context mContext;
+ private LayoutInflater mInflater;
+ private ViewPager mPager;
+
+ private String mComponentType;
+ private CursorPagerAdapter mAdapter;
+ private int mBatteryStyle;
+ private int mItemsPerPage;
+ private String mAppliedComponentPkgName;
+
+ // 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;
+
+ 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);
+ mBatteryStyle = Settings.System.getInt(context.getContentResolver(),
+ Settings.System.STATUS_BAR_BATTERY, 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) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ setVisibility(View.GONE);
+ if (mOpenCloseListener != null) mOpenCloseListener.onSelectorClosed();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+ mMediaPlayer = new MediaPlayer();
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mPager = (ViewPager) findViewById(R.id.pager);
+ mAdapter = new CursorPagerAdapter<View>(null, mItemsPerPage);
+ mPager.setAdapter(mAdapter);
+ PageIndicator indicator = (PageIndicator) findViewById(R.id.page_indicator);
+ indicator.setViewPager(mPager);
+
+ // set navbar_padding to GONE if no on screen navigation bar is available
+ if (!Utils.hasNavigationBar(mContext)) {
+ findViewById(R.id.navbar_padding).setVisibility(View.GONE);
+ }
+ setEnabled(false);
+ }
+
+ public void setComponentType(String component) {
+ // Find out which theme is currently applied for this component
+ String selection = MixnMatchColumns.COL_KEY + "=?";
+ String[] selectionArgs = {MixnMatchColumns.componentToMixNMatchKey(component)};
+ Cursor c = mContext.getContentResolver().query(MixnMatchColumns.CONTENT_URI,
+ null, selection, selectionArgs, null);
+ if (c != null) {
+ if (c.moveToFirst()) {
+ mAppliedComponentPkgName = c.getString(
+ c.getColumnIndex(MixnMatchColumns.COL_VALUE));
+ }
+ c.close();
+ } else {
+ mAppliedComponentPkgName = null;
+ }
+ if (mComponentType == null || !mComponentType.equals(component)){
+ mAdapter.swapCursor(null);
+ }
+ mComponentType = component;
+ ((FragmentActivity) mContext).getSupportLoaderManager().restartLoader(
+ getLoaderIdFromComponent(component), null, this);
+ }
+
+ public String getComponentType() {
+ return mComponentType;
+ }
+
+ public void setNumItemsPerPage(int itemsPerPage) {
+ if (mItemsPerPage != itemsPerPage) {
+ mItemsPerPage = itemsPerPage;
+ mAdapter.setNumItemsPerPage(mItemsPerPage);
+ }
+ }
+
+ public void setHeight(int height) {
+ ViewGroup.LayoutParams params = mPager.getLayoutParams();
+ if (params.height != height) {
+ params.height = height;
+ mPager.setLayoutParams(params);
+ requestLayout();
+ }
+ }
+
+ public void show(String componentType, int itemsPerPage, int height) {
+ setNumItemsPerPage(itemsPerPage);
+ setHeight(height);
+ setComponentType(componentType);
+ 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_ICON;
+ }
+ 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_BOOTANIMATIONS;
+ }
+ 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<Cursor> onCreateLoader(int id, Bundle args) {
+ 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:
+ selection = MODIFIES_FONTS + "=?";
+ break;
+ case LOADER_ID_ICON:
+ 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 + "=?";
+ projection = new String[] {
+ PreviewColumns.WALLPAPER_THUMBNAIL,
+ ThemesColumns.TITLE,
+ ThemesColumns.PKG_NAME
+ };
+ break;
+ case LOADER_ID_BOOTANIMATIONS:
+ 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 + "=?";
+ projection = new String[] {
+ PreviewColumns.LOCK_WALLPAPER_THUMBNAIL,
+ ThemesColumns.TITLE,
+ 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(mContext, PreviewColumns.CONTENT_URI,
+ projection, selection, selectionArgs, sortOrder);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, final Cursor data) {
+ mAdapter.swapCursor(data);
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {
+ mAdapter.swapCursor(null);
+ }
+
+ public void setOnItemClickedListener(OnItemClickedListener listener) {
+ mListener = listener;
+ }
+
+ public void setOnOpenCloseListener(OnOpenCloseListener listener) {
+ mOpenCloseListener = listener;
+ }
+
+ public class CursorPagerAdapter<T extends View> extends PagerAdapter {
+ LinearLayout.LayoutParams mItemParams =
+ new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);
+ LinearLayout.LayoutParams mSoundItemParams =
+ new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 0, 1.0f);
+ private Cursor mCursor;
+ private int mItemsPerPage;
+ private TypefaceHelperCache mTypefaceCache;
+
+ public CursorPagerAdapter(Cursor cursor, int itemsPerPage) {
+ super();
+ mCursor = cursor;
+ mItemsPerPage = itemsPerPage;
+ mTypefaceCache = TypefaceHelperCache.getInstance();
+ }
+
+ public void setNumItemsPerPage(int itemsPerPage) {
+ mItemsPerPage = itemsPerPage;
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ ViewGroup v = (ViewGroup) mInflater.inflate(R.layout.component_selection_pager_item,
+ container, false);
+ if (v instanceof LinearLayout) {
+ ((LinearLayout) v).setWeightSum(mItemsPerPage);
+ }
+ if (MODIFIES_STATUS_BAR.equals(mComponentType)) {
+ newStatusBarView(mCursor, v, position);
+ }
+ if (MODIFIES_NAVIGATION_BAR.equals(mComponentType)) {
+ newNavBarView(mCursor, v, position);
+ }
+ if (MODIFIES_FONTS.equals(mComponentType)) {
+ newFontView(mCursor, v, position);
+ }
+ if (MODIFIES_ICONS.equals(mComponentType)) {
+ newIconView(mCursor, v, position);
+ }
+ if (MODIFIES_OVERLAYS.equals(mComponentType)) {
+ newStyleView(mCursor, v, position);
+ }
+ if (MODIFIES_LAUNCHER.equals(mComponentType)) {
+ newWallpapersView(mCursor, v, position);
+ }
+ if (MODIFIES_BOOT_ANIM.equals(mComponentType)) {
+ newBootanimationView(mCursor, v, position);
+ }
+ if (MODIFIES_RINGTONES.equals(mComponentType) ||
+ MODIFIES_NOTIFICATIONS.equals(mComponentType) ||
+ MODIFIES_ALARMS.equals(mComponentType)) {
+ v = (ViewGroup) mInflater.inflate(R.layout.component_selection_sounds_pager_item,
+ container, false);
+ if (v instanceof LinearLayout) {
+ ((LinearLayout) v).setWeightSum(mItemsPerPage);
+ }
+ newSoundView(mCursor, v, position, mComponentType);
+ }
+ if (MODIFIES_LOCKSCREEN.equals(mComponentType)) {
+ newLockScreenView(mCursor, v, position);
+ }
+ container.addView(v);
+ return v;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ if (object instanceof View) {
+ container.removeView((View) object);
+ }
+ }
+
+ @Override
+ public int getCount() {
+ return mCursor == null ? 0 : (int) Math.ceil((float)mCursor.getCount() / mItemsPerPage);
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public int getItemPosition(Object object) {
+ return POSITION_NONE;
+ }
+
+ public void swapCursor(Cursor c) {
+ mCursor = c;
+ notifyDataSetChanged();
+ mPager.setCurrentItem(0, false);
+ }
+
+ public Cursor getCursor() {
+ return mCursor;
+ }
+
+ private OnClickListener mItemClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String pkgName = (String) v.getTag();
+ if (DEBUG_SELECTOR) Toast.makeText(mContext, pkgName, Toast.LENGTH_SHORT).show();
+ if (mListener != null) {
+ mListener.onItemClicked(pkgName);
+ }
+ }
+ };
+
+ private void newStatusBarView(Cursor cursor, ViewGroup parent, int position) {
+ 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.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(cursor.getString(pkgNameIndex));
+ v.setOnClickListener(mItemClickListener);
+ parent.addView(v, mItemParams);
+ addDividerIfNeeded(parent, i, index, cursor);
+ }
+ }
+
+ private void newNavBarView(Cursor cursor, ViewGroup parent, int position) {
+ 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.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(cursor.getString(pkgNameIndex));
+ v.setOnClickListener(mItemClickListener);
+ parent.addView(v, mItemParams);
+ addDividerIfNeeded(parent, i, index, cursor);
+ }
+ }
+
+ private void newFontView(Cursor cursor, ViewGroup parent, int position) {
+ 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.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(cursor.getString(pkgNameIndex));
+ v.setOnClickListener(mItemClickListener);
+ parent.addView(v, mItemParams);
+ addDividerIfNeeded(parent, i, index, cursor);
+ }
+ }
+
+ private void newIconView(Cursor cursor, ViewGroup parent, int position) {
+ 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.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(cursor.getString(pkgNameIndex));
+ v.setOnClickListener(mItemClickListener);
+ parent.addView(v, mItemParams);
+ addDividerIfNeeded(parent, i, index, cursor);
+ }
+ }
+
+ private void newStyleView(Cursor cursor, ViewGroup parent, int position) {
+ 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.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(cursor.getString(pkgNameIndex));
+ v.setOnClickListener(mItemClickListener);
+ parent.addView(v, mItemParams);
+ addDividerIfNeeded(parent, i, index, cursor);
+ }
+ }
+
+ private void newWallpapersView(Cursor cursor, ViewGroup parent, int position) {
+ 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.wallpaper_component_selection_item, parent,
+ false);
+ int wallpaperIndex = cursor.getColumnIndex(PreviewColumns.WALLPAPER_THUMBNAIL);
+ int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME);
+
+ ((ImageView) v.findViewById(R.id.icon)).setImageBitmap(
+ Utils.loadBitmapBlob(cursor, wallpaperIndex));
+ setTitle(((TextView) v.findViewById(R.id.title)), cursor);
+ v.setTag(cursor.getString(pkgNameIndex));
+ v.setOnClickListener(mItemClickListener);
+ parent.addView(v, mItemParams);
+ addDividerIfNeeded(parent, i, index, cursor);
+ }
+ }
+
+ private void newLockScreenView(Cursor cursor, ViewGroup parent, int position) {
+ 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.wallpaper_component_selection_item, parent,
+ false);
+ int wallpaperIndex = cursor.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_THUMBNAIL);
+ int pkgNameIndex = cursor.getColumnIndex(ThemesContract.ThemesColumns.PKG_NAME);
+
+ ((ImageView) v.findViewById(R.id.icon)).setImageBitmap(
+ Utils.loadBitmapBlob(cursor, wallpaperIndex));
+ setTitle(((TextView) v.findViewById(R.id.title)), cursor);
+ v.setTag(cursor.getString(pkgNameIndex));
+ v.setOnClickListener(mItemClickListener);
+ parent.addView(v, mItemParams);
+ addDividerIfNeeded(parent, i, index, cursor);
+ }
+ }
+
+ private void newBootanimationView(Cursor cursor, ViewGroup parent, int position) {
+ 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.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(cursor.getString(pkgNameIndex));
+ v.setOnClickListener(mItemClickListener);
+ parent.addView(v, mItemParams);
+ addDividerIfNeeded(parent, i, index, cursor);
+ }
+ }
+
+ private void newSoundView(Cursor cursor, ViewGroup parent, int position, String component) {
+ 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(cursor.getString(pkgNameIndex));
+ v.setOnClickListener(mItemClickListener);
+ parent.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 (mComponentType.equals(MODIFIES_RINGTONES)) {
+ type = RingtoneManager.TYPE_RINGTONE;
+ } else if (mComponentType.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);
+ }
+ }
+ });
+ }
+ }
+
+ private void addDivider(ViewGroup parent) {
+ final Resources res = getResources();
+ View v = mInflater.inflate(R.layout.component_divider, parent, false);
+ LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) v.getLayoutParams();
+ // Boot animations require a taller divider so adjust accordingly
+ if (ThemesColumns.MODIFIES_BOOT_ANIM.equals(mComponentType)) {
+ params.topMargin = res.getDimensionPixelSize(
+ R.dimen.component_divider_margin_top_bootani);
+ params.height = res.getDimensionPixelSize(R.dimen.component_divider_height_bootani);
+ }
+ v.setLayoutParams(params);
+ parent.addView(v);
+ }
+
+ private void addDividerIfNeeded(ViewGroup parent, int position, int cursorIndex,
+ Cursor cursor) {
+ if (position < mItemsPerPage - 1 && cursorIndex < cursor.getCount() - 1) {
+ addDivider(parent);
+ }
+ }
+
+ private void setTitle(TextView titleView, Cursor cursor) {
+ String pkgName = cursor.getString(cursor.getColumnIndex(ThemesColumns.PKG_NAME));
+ titleView.setText(cursor.getString(cursor.getColumnIndex(ThemesColumns.TITLE)));
+ if (pkgName.equals(mAppliedComponentPkgName)) {
+ titleView.setTextColor(getResources().getColor(
+ R.color.component_selection_current_text_color));
+ }
+ }
+ }
+
+ public interface OnItemClickedListener {
+ public void onItemClicked(String pkgName);
+ }
+
+ public interface OnOpenCloseListener {
+ public void onSelectorOpened();
+ public void onSelectorClosed();
+ }
+} \ No newline at end of file
diff --git a/src/com/cyngn/theme/chooser/IconTransitionDrawable.java b/src/com/cyngn/theme/chooser/IconTransitionDrawable.java
new file mode 100644
index 0000000..a62fb07
--- /dev/null
+++ b/src/com/cyngn/theme/chooser/IconTransitionDrawable.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2014 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 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()}.
+ * <p>
+ * It can be defined in an XML file with the <code>&lt;transition></code> element.
+ * Each Drawable in the transition is defined in a nested <code>&lt;item></code>. For more
+ * information, see the guide to <a
+ * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
+ *
+ * @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/NotificationHijackingService.java b/src/com/cyngn/theme/chooser/NotificationHijackingService.java
new file mode 100644
index 0000000..910a2de
--- /dev/null
+++ b/src/com/cyngn/theme/chooser/NotificationHijackingService.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 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 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.themeInfos != null && pi.themeInfos.length > 0) ||
+ (pi.legacyThemeInfos != null && pi.legacyThemeInfos.length > 0)) {
+ 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
new file mode 100644
index 0000000..085ea68
--- /dev/null
+++ b/src/com/cyngn/theme/chooser/PagerContainer.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2012 Wireless Designs, LLC
+ * Portions copyright (C) 2014, The CyanogenMod Project
+ *
+ *
+ * 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
new file mode 100644
index 0000000..f6823ea
--- /dev/null
+++ b/src/com/cyngn/theme/chooser/ThemeFragment.java
@@ -0,0 +1,1902 @@
+/*
+ * Copyright (C) 2014 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 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.app.WallpaperManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ThemeUtils;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.ThemeConfig;
+import android.content.res.ThemeManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+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.Bundle;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.provider.Settings;
+import android.provider.ThemesContract;
+import android.provider.ThemesContract.PreviewColumns;
+import android.provider.ThemesContract.ThemesColumns;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.CursorLoader;
+import android.support.v4.content.Loader;
+import android.util.Log;
+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.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.ScrollView;
+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.IconPreviewHelper;
+import com.cyngn.theme.util.ThemedTypefaceHelper;
+import com.cyngn.theme.util.TypefaceHelperCache;
+import com.cyngn.theme.util.Utils;
+import com.cyngn.theme.widget.BootAniImageView;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipFile;
+
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_ALARMS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_BOOT_ANIM;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_LAUNCHER;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_LOCKSCREEN;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_NOTIFICATIONS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_OVERLAYS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_RINGTONES;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_STATUS_BAR;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_NAVIGATION_BAR;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_ICONS;
+import static android.provider.ThemesContract.ThemesColumns.MODIFIES_FONTS;
+
+public class ThemeFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor>,
+ ThemeManager.ThemeChangeListener {
+ 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 REQUEST_UNINSTALL = 1; // Request code
+
+ private static final String NAVIGATION_BAR_BACKGROUND = "navbar_background";
+
+ 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_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 int LOADER_ID_ALL = 0;
+ private static final int LOADER_ID_STATUS_BAR = 1;
+ private static final int LOADER_ID_FONT = 2;
+ private static final int LOADER_ID_ICONS = 3;
+ private static final int LOADER_ID_WALLPAPER = 4;
+ private static final int LOADER_ID_NAVIGATION_BAR = 5;
+ private static final int LOADER_ID_LOCKSCREEN = 6;
+ private static final int LOADER_ID_STYLE = 7;
+ private static final int LOADER_ID_BOOT_ANIMATION = 8;
+ private static final int LOADER_ID_RINGTONE = 9;
+ private static final int LOADER_ID_NOTIFICATION = 10;
+ private static final int LOADER_ID_ALARM = 11;
+
+ private static ComponentName[] sIconComponents;
+
+ private static TypefaceHelperCache sTypefaceHelperCache;
+
+ /**
+ * Maps the card's resource ID to a theme component
+ */
+ private final SparseArray<String> mCardIdsToComponentTypes = new SparseArray<String>();
+
+ private String mPkgName;
+ private Typeface mTypefaceNormal;
+ private int mBatteryStyle;
+
+ private ScrollView mScrollView;
+ private ViewGroup mScrollContent;
+ private ViewGroup mPreviewContent; // Contains icons, font, nav/status etc. Not wallpaper
+ private View mLoadingView;
+
+ //Status Bar Views
+ private ImageView mBluetooth;
+ private ImageView mWifi;
+ private ImageView mSignal;
+ private ImageView mBattery;
+ private TextView mClock;
+
+ // Other Misc Preview Views
+ private FrameLayout mShadowFrame;
+ private ImageView mWallpaper;
+ private ViewGroup mStatusBar;
+ private TextView mFontPreview;
+ private ViewGroup mIconContainer;
+ private ViewGroup mStyleContainer;
+ private ViewGroup mBootAnimationContainer;
+ private BootAniImageView mBootAnimation;
+
+ // Nav Bar Views
+ private ViewGroup mNavBar;
+ private ImageView mBackButton;
+ private ImageView mHomeButton;
+ private ImageView mRecentButton;
+
+ // Title Card Views
+ private ViewGroup mTitleCard;
+ private ViewGroup mTitleLayout;
+ private TextView mTitle;
+ private ImageView mApply;
+ private ImageView mOverflow;
+ private ProgressBar mProgress;
+
+ // Additional Card Views
+ private LinearLayout mAdditionalCards;
+ private WallpaperCardView mWallpaperCard;
+ private WallpaperCardView mLockScreenCard;
+
+ // Style views
+ private ImageView mStylePreview;
+
+ // Sound cards
+ private ViewGroup mRingtoneContainer;
+ private ImageView mRingtonePlayPause;
+ private ViewGroup mNotificationContainer;
+ private ImageView mNotificationPlayPause;
+ private ViewGroup mAlarmContainer;
+ private ImageView mAlarmPlayPause;
+ private Map<ImageView, MediaPlayer> mMediaPlayers;
+
+ private Handler mHandler;
+
+ private boolean mIsUninstalled;
+ private int mActiveCardId = -1;
+ private ComponentSelector mSelector;
+ // Supported components for the theme this fragment represents
+ private Map<String, String> mSelectedComponentsMap = new HashMap<String, String>();
+ // Current system theme configuration as component -> pkgName
+ private Map<String, String> mCurrentTheme = new HashMap<String, String>();
+
+ static ThemeFragment newInstance(String pkgName) {
+ if (sTypefaceHelperCache == null) {
+ sTypefaceHelperCache = TypefaceHelperCache.getInstance();
+ }
+ ThemeFragment f = new ThemeFragment();
+ Bundle args = new Bundle();
+ args.putString("pkgName", pkgName);
+ 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("pkgName");
+ mBatteryStyle = Settings.System.getInt(context.getContentResolver(),
+ Settings.System.STATUS_BAR_BATTERY, 0);
+
+ getIconComponents(context);
+ ThemedTypefaceHelper helper = sTypefaceHelperCache.getHelperForTheme(context,
+ CURRENTLY_APPLIED_THEME.equals(mPkgName) ? getAppliedFontPackageName() : 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<ImageView, MediaPlayer>(3);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.fragment_pager_list, container, false);
+
+ mScrollView = (ScrollView) v.findViewById(android.R.id.list);
+ mScrollContent = (ViewGroup) mScrollView.getChildAt(0);
+ mPreviewContent = (ViewGroup) v.findViewById(R.id.preview_container);
+ mLoadingView = v.findViewById(R.id.loading_view);
+
+ // Status Bar
+ 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);
+ mFontPreview = (TextView) v.findViewById(R.id.font_preview);
+ mFontPreview.setTypeface(mTypefaceNormal);
+ mIconContainer = (ViewGroup) v.findViewById(R.id.icon_container);
+ mShadowFrame = (FrameLayout) v.findViewById(R.id.shadow_frame);
+ mStyleContainer = (ViewGroup) v.findViewById(R.id.style_card);
+ mStylePreview = (ImageView) v.findViewById(R.id.style_preview);
+ mBootAnimationContainer = (ViewGroup) v.findViewById(R.id.bootani_preview_container);
+ mBootAnimation =
+ (BootAniImageView) mBootAnimationContainer.findViewById(R.id.bootani_preview);
+ mRingtoneContainer = (ViewGroup) v.findViewById(R.id.ringtone_preview_container);
+ mRingtonePlayPause = (ImageView) mRingtoneContainer.findViewById(R.id.play_pause);
+ mNotificationContainer = (ViewGroup) v.findViewById(R.id.notification_preview_container);
+ mNotificationPlayPause = (ImageView) mNotificationContainer.findViewById(R.id.play_pause);
+ mAlarmContainer = (ViewGroup) v.findViewById(R.id.alarm_preview_container);
+ mAlarmPlayPause = (ImageView) mAlarmContainer.findViewById(R.id.play_pause);
+
+ // Nav Bar
+ 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);
+ 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) {
+ PopupMenu popupmenu = new PopupMenu(getActivity(), mTitleCard, Gravity.END);
+ popupmenu.getMenuInflater().inflate(R.menu.overflow, popupmenu.getMenu());
+
+ if (CURRENTLY_APPLIED_THEME.equals(mPkgName) ||
+ mPkgName.equals(ThemeUtils.getDefaultThemePackageName(getActivity())) ||
+ mPkgName.equals(ThemeConfig.HOLO_DEFAULT)) {
+ Menu menu = popupmenu.getMenu();
+ menu.findItem(R.id.menu_delete).setEnabled(false);
+ }
+
+ popupmenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.menu_author:
+ Toast.makeText(getActivity(),
+ "Not supported",
+ Toast.LENGTH_LONG).show();
+ break;
+ case R.id.menu_delete:
+ uninstallTheme();
+ break;
+ }
+
+ return true;
+ }
+ });
+ popupmenu.show();
+ }
+ });
+ mApply = (ImageView) v.findViewById(R.id.apply);
+ mApply.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ applyTheme();
+ }
+ });
+ if (CURRENTLY_APPLIED_THEME.equals(mPkgName)) {
+ mApply.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);
+
+ getLoaderManager().initLoader(LOADER_ID_ALL, null, this);
+
+ initCards(v);
+
+ return v;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (CURRENTLY_APPLIED_THEME.equals(mPkgName)) {
+ if (getLoaderManager().getLoader(0) != null) {
+ getLoaderManager().restartLoader(0, null, this);
+ }
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ stopMediaPlayers();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ freeMediaPlayers();
+ }
+
+ @Override
+ public void onProgress(int progress) {
+ mProgress.setProgress(progress);
+ }
+
+ @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) {
+ mProgress.setProgress(100);
+ animateProgressOut();
+ ((ChooserActivity) getActivity()).themeChangeEnded();
+ }
+ }
+
+ public void expand() {
+ // 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);
+ mIconContainer.setPadding(padding.left, padding.top, padding.right, padding.bottom);
+ mShadowFrame.setBackground(null);
+ mShadowFrame.setPadding(0, 0, 0, 0);
+ mAdditionalCards.setPadding(padding.left, padding.top, padding.right, padding.bottom);
+
+ // 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();
+ 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 = ((ChooserActivity) getActivity()).getComponentSelector();
+ mSelector.setOnItemClickedListener(mOnComponentItemClicked);
+ if (mBootAnimation != null) mBootAnimation.start();
+ }
+
+
+
+ // Returns the boundaries for all the children of parent relative to the app window
+ private List<Rect> getChildrensGlobalBounds(ViewGroup parent) {
+ List<Rect> bounds = new ArrayList<Rect>();
+ 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 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) {
+ // 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);
+
+ //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);
+ 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);
+
+ 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();
+ }
+ }
+
+ mScrollView.requestLayout();
+ animateChildren(false, getChildrensGlobalBounds(mPreviewContent));
+ animateExtras(false);
+ animateWallpaperIn();
+ animateTitleCard(false, applyTheme);
+ if (mBootAnimation != null) mBootAnimation.stop();
+ stopMediaPlayers();
+ }
+
+ // 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<Rect> prevBounds) {
+ final ViewGroup root = (ViewGroup) getActivity().getWindow()
+ .getDecorView().findViewById(android.R.id.content);
+
+ // 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;
+ v.setTranslationY((prevY - endY - paddingTop) + (prevHeight - endHeight) / 2);
+ 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("system_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 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);
+ if (expand) {
+ mTitleCard.setVisibility(View.GONE);
+ } else if (applyTheme) {
+ // since the title card is the last animation when collapsing
+ // 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);
+ }
+
+ private String getAppliedFontPackageName() {
+ final Configuration config = getActivity().getResources().getConfiguration();
+ final ThemeConfig themeConfig = config != null ? config.themeConfig : null;
+ return themeConfig != null ? themeConfig.getFontPkgName() :
+ ThemeConfig.getSystemTheme().getFontPkgName();
+ }
+
+ private ThemeManager getThemeManager() {
+ final Context context = getActivity();
+ if (context != null) {
+ return (ThemeManager) context.getSystemService(Context.THEME_SERVICE);
+ }
+ return null;
+ }
+
+ private void freeMediaPlayers() {
+ for (MediaPlayer mp : mMediaPlayers.values()) {
+ if (mp != null) {
+ mp.stop();
+ mp.release();
+ }
+ }
+ mMediaPlayers.clear();
+ }
+
+ private 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();
+ }
+ }
+ }
+ };
+
+ private 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);
+ }
+ }
+ }
+
+ @Override
+ public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+ String pkgName = mPkgName;
+ if (args != null) {
+ pkgName = args.getString("pkgName");
+ }
+ Uri uri = ThemesContract.PreviewColumns.CONTENT_URI;
+ String selection = ThemesContract.ThemesColumns.PKG_NAME + "= ?";
+ String[] selectionArgs = new String[] { pkgName };
+ String[] projection = null;
+ switch (id) {
+ case LOADER_ID_ALL:
+ if (!CURRENTLY_APPLIED_THEME.equals(pkgName)) {
+ projection = new String[] {
+ ThemesColumns.PKG_NAME,
+ ThemesColumns.TITLE,
+ ThemesColumns.WALLPAPER_URI,
+ ThemesColumns.HOMESCREEN_URI,
+ // 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
+ };
+ } else {
+ 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,
+ // TODO: add this to the ThemesContract if this
+ // design moves beyond prototype
+ NAVIGATION_BAR_BACKGROUND
+ };
+ uri = PreviewColumns.APPLIED_URI;
+ selection = null;
+ selectionArgs = null;
+ }
+ 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:
+ 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,
+ PreviewColumns.ICON_PREVIEW_4
+ };
+ break;
+ case LOADER_ID_WALLPAPER:
+ 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_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(getActivity(), uri, projection, selection, selectionArgs, null);
+ }
+
+ @Override
+ public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
+ c.moveToFirst();
+ if (c.getCount() == 0) return;
+ switch (loader.getId()) {
+ case LOADER_ID_ALL:
+ populateSupportedComponents(c);
+ loadStatusBar(c, false);
+ loadIcons(c, false);
+ loadNavBar(c, false);
+ loadTitle(c);
+ loadFont(c, false);
+ loadAndRemoveAdditionalCards(c);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ animateContentIn();
+ }
+ });
+ break;
+ case LOADER_ID_STATUS_BAR:
+ loadStatusBar(c, true);
+ break;
+ case LOADER_ID_FONT:
+ loadFont(c, true);
+ break;
+ case LOADER_ID_ICONS:
+ loadIcons(c, true);
+ break;
+ case LOADER_ID_WALLPAPER:
+ loadWallpaper(c, true);
+ break;
+ case LOADER_ID_NAVIGATION_BAR:
+ loadNavBar(c, true);
+ break;
+ case LOADER_ID_LOCKSCREEN:
+ loadLockScreen(c, true);
+ break;
+ case LOADER_ID_STYLE:
+ loadStyle(c, true);
+ break;
+ case LOADER_ID_BOOT_ANIMATION:
+ loadBootAnimation(c, true);
+ break;
+ case LOADER_ID_RINGTONE:
+ loadAudible(RingtoneManager.TYPE_RINGTONE, c, true);
+ break;
+ case LOADER_ID_NOTIFICATION:
+ loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, true);
+ break;
+ case LOADER_ID_ALARM:
+ loadAudible(RingtoneManager.TYPE_ALARM, c, true);
+ break;
+ }
+ }
+
+ @Override
+ public void onLoaderReset(Loader<Cursor> loader) {}
+
+ private void loadAndRemoveAdditionalCards(Cursor c) {
+ LinkedList<View> removeList = new LinkedList<View>();
+ for(int i=0; i < mAdditionalCards.getChildCount(); i++) {
+ View v = mAdditionalCards.getChildAt(i);
+ if (v instanceof ComponentCardView) {
+ String component = mCardIdsToComponentTypes.get(v.getId());
+ if (shouldShowComponentCard(component)) {
+ loadAdditionalCard(c, component);
+ } else {
+ removeList.add(v);
+ // remove the Space below this card
+ removeList.add(mAdditionalCards.getChildAt(i+1));
+
+ if (component != null && !shouldShowComponentCard(component)) {
+ mCardIdsToComponentTypes.remove(v.getId());
+ }
+ }
+ }
+ }
+
+ // TODO: It might make more sense to not inflate so many views if we do not
+ // plan to actually use them. But it is nice to be able to declare them in the
+ // xml layout under additionalCards
+ for(View v : removeList) {
+ mAdditionalCards.removeView(v);
+ }
+
+ if (shouldShowComponentCard(MODIFIES_LOCKSCREEN)) {
+ loadLockScreen(c, false);
+ } else {
+ mAdditionalCards.removeView(mLockScreenCard);
+ }
+ }
+
+ private void loadAdditionalCard(Cursor c, String component) {
+ if (MODIFIES_LOCKSCREEN.equals(component)) {
+ loadLockScreen(c, false);
+ } else if (MODIFIES_LAUNCHER.equals(component)) {
+ loadWallpaper(c, false);
+ } else if (MODIFIES_OVERLAYS.equals(component)) {
+ loadStyle(c, false);
+ } else if (MODIFIES_BOOT_ANIM.equals(component)) {
+ loadBootAnimation(c, false);
+ } else if (MODIFIES_RINGTONES.equals(component)) {
+ loadAudible(RingtoneManager.TYPE_RINGTONE, c, false);
+ } else if (MODIFIES_NOTIFICATIONS.equals(component)) {
+ loadAudible(RingtoneManager.TYPE_NOTIFICATION, c, false);
+ } else if (MODIFIES_ALARMS.equals(component)) {
+ loadAudible(RingtoneManager.TYPE_ALARM, c, false);
+ } else {
+ throw new IllegalArgumentException("Don't know how to load: " + component);
+ }
+ }
+
+ private void populateSupportedComponents(Cursor c) {
+ // Currently applied theme doesn't return the columns we expect and
+ // it doesn't matter because currently applied theme shows everything anyhow
+ if (CURRENTLY_APPLIED_THEME.equals(mPkgName)) {
+ return;
+ }
+
+ List<String> 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 = c.getInt(modifiesCompIdx) == 1;
+ if (supported) {
+ mSelectedComponentsMap.put(component, pkg);
+ }
+ }
+ if (!mSelectedComponentsMap.containsKey(MODIFIES_BOOT_ANIM)) {
+ mBootAnimation = null;
+ }
+ }
+
+ /**
+ * 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
+ *
+ */
+ private Boolean shouldShowComponentCard(String component) {
+ if (CURRENTLY_APPLIED_THEME.equals(mPkgName)) {
+ return true;
+ }
+ String pkg = mSelectedComponentsMap.get(component);
+ return pkg != null && pkg.equals(mPkgName);
+ }
+
+ private void loadTitle(Cursor c) {
+ if (CURRENTLY_APPLIED_THEME.equals(mPkgName)) {
+ mTitle.setText(R.string.my_theme);
+ } else {
+ int titleIdx = c.getColumnIndex(ThemesColumns.TITLE);
+ String title = c.getString(titleIdx);
+ mTitle.setText(title);
+ }
+ }
+
+ private void loadWallpaper(Cursor c, boolean animate) {
+ Drawable overlay = null;
+ if (animate) {
+ overlay = getOverlayDrawable(mWallpaperCard, true);
+ }
+
+ int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME);
+ int wpIdx = c.getColumnIndex(PreviewColumns.WALLPAPER_PREVIEW);
+ final Resources res = getResources();
+ if (pkgNameIdx > -1) {
+ Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx);
+ mWallpaper.setImageBitmap(bitmap);
+ mWallpaperCard.setWallpaper(new BitmapDrawable(res, bitmap));
+ String pkgName = c.getString(pkgNameIdx);
+ mSelectedComponentsMap.put(MODIFIES_LAUNCHER, pkgName);
+ } else {
+ final Context context = getActivity();
+ Drawable wp = context == null ? null :
+ WallpaperManager.getInstance(context).getDrawable();
+ if (wp == null) {
+ wp = new BitmapDrawable(res, Utils.loadBitmapBlob(c, wpIdx));
+ }
+ mWallpaper.setImageDrawable(wp);
+ mWallpaperCard.setWallpaper(wp);
+ }
+
+ if (animate) {
+ animateContentChange(R.id.wallpaper_card, mWallpaperCard, overlay);
+ }
+ }
+
+ public void loadLockScreen(Cursor c, boolean animate) {
+ Drawable overlay = null;
+ if (animate) {
+ overlay = getOverlayDrawable(mLockScreenCard, true);
+ }
+
+ int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME);
+ int wpIdx = c.getColumnIndex(PreviewColumns.LOCK_WALLPAPER_PREVIEW);
+ final Resources res = getResources();
+ if (pkgNameIdx > -1) {
+ Bitmap bitmap = Utils.loadBitmapBlob(c, wpIdx);
+ mLockScreenCard.setWallpaper(new BitmapDrawable(res, bitmap));
+ String pkgName = c.getString(pkgNameIdx);
+ mSelectedComponentsMap.put(MODIFIES_LOCKSCREEN, pkgName);
+ } else {
+ final Context context = getActivity();
+ Drawable wp = context == null ? null :
+ WallpaperManager.getInstance(context).getFastKeyguardDrawable();
+ if (wp == null) {
+ wp = new BitmapDrawable(res, Utils.loadBitmapBlob(c, wpIdx));
+ }
+ mLockScreenCard.setWallpaper(wp);
+ }
+
+ if (animate) {
+ animateContentChange(R.id.lockscreen_card, mLockScreenCard, overlay);
+ }
+ }
+
+ private 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 = c.getInt(wifiMarginIdx);
+ int clockTextColor = c.getInt(clockColorIdx);
+
+ Drawable overlay = null;
+ if (animate) {
+ overlay = getOverlayDrawable(mStatusBar, 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);
+ mSelectedComponentsMap.put(MODIFIES_STATUS_BAR, pkgName);
+ }
+ if (animate) {
+ animateContentChange(R.id.status_bar_container, mStatusBar, overlay);
+ }
+ }
+
+ private void loadIcons(Cursor c, boolean animate) {
+ int[] iconIdx = new int[4];
+ iconIdx[0] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_1);
+ iconIdx[1] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_2);
+ iconIdx[2] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_3);
+ iconIdx[3] = c.getColumnIndex(PreviewColumns.ICON_PREVIEW_4);
+ 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(), "");
+ int numOfChildren = ((ViewGroup)mIconContainer.getChildAt(1)).getChildCount();
+
+ List<ImageView> iconViews = new ArrayList<ImageView>(numOfChildren);
+ for(int i=0; i < numOfChildren; i++) {
+ final View view = (View) ((ViewGroup)mIconContainer.getChildAt(1)).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);
+ mSelectedComponentsMap.put(MODIFIES_ICONS, pkgName);
+ }
+ }
+
+ private 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(NAVIGATION_BAR_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);
+ }
+
+ mNavBar.setBackground(new BitmapDrawable(getActivity().getResources(), background));
+ mBackButton.setImageBitmap(backButton);
+ mHomeButton.setImageBitmap(homeButton);
+ mRecentButton.setImageBitmap(recentButton);
+
+ if (pkgNameIdx > -1) {
+ String pkgName = c.getString(pkgNameIdx);
+ mSelectedComponentsMap.put(MODIFIES_NAVIGATION_BAR, pkgName);
+ }
+ if (animate) {
+ animateContentChange(R.id.navigation_bar_container, mNavBar, overlay);
+ }
+ }
+
+ private void loadFont(Cursor c, boolean animate) {
+ Drawable overlay = null;
+ if (animate) {
+ overlay = getOverlayDrawable(mFontPreview, true);
+ }
+
+ int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME);
+ String pkgName = pkgNameIdx >= 0 ? c.getString(pkgNameIdx) : mPkgName;
+ TypefaceHelperCache cache = TypefaceHelperCache.getInstance();
+ ThemedTypefaceHelper helper = cache.getHelperForTheme(getActivity(),
+ CURRENTLY_APPLIED_THEME.equals(pkgName) ? getAppliedFontPackageName() : pkgName);
+ mTypefaceNormal = helper.getTypeface(Typeface.NORMAL);
+ mFontPreview.setTypeface(mTypefaceNormal);
+ if (pkgNameIdx > -1) {
+ mSelectedComponentsMap.put(MODIFIES_FONTS, pkgName);
+ }
+ if (animate) {
+ animateContentChange(R.id.font_preview_container, mFontPreview, overlay);
+ }
+ }
+
+ private void loadStyle(Cursor c, boolean animate) {
+ Drawable overlay = null;
+ if (animate) {
+ overlay = getOverlayDrawable(mStylePreview, true);
+ }
+
+ 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);
+ mSelectedComponentsMap.put(MODIFIES_OVERLAYS, pkgName);
+ }
+ if (animate) {
+ animateContentChange(R.id.style_card, mStylePreview, overlay);
+ }
+ }
+
+ private void loadBootAnimation(Cursor c, boolean autoStart) {
+ int pkgNameIdx = c.getColumnIndex(ThemesColumns.PKG_NAME);
+ if (mBootAnimation != null) {
+ String pkgName;
+ if (pkgNameIdx > -1) {
+ pkgName = c.getString(pkgNameIdx);
+ mSelectedComponentsMap.put(MODIFIES_BOOT_ANIM, pkgName);
+ } else {
+ pkgName = mCurrentTheme.get(MODIFIES_BOOT_ANIM);
+ }
+ mBootAnimation.stop();
+ new AnimationLoader(getActivity(), pkgName, mBootAnimation, autoStart).execute();
+ }
+ }
+
+ private void loadAudible(int type, Cursor c, boolean animate) {
+ View audibleContainer = null;
+ ImageView playPause = null;
+ String component = null;
+ switch (type) {
+ case RingtoneManager.TYPE_RINGTONE:
+ audibleContainer = mRingtoneContainer;
+ playPause = mRingtonePlayPause;
+ component = MODIFIES_RINGTONES;
+ break;
+ case RingtoneManager.TYPE_NOTIFICATION:
+ audibleContainer = mNotificationContainer;
+ playPause = mNotificationPlayPause;
+ component = MODIFIES_NOTIFICATIONS;
+ break;
+ case RingtoneManager.TYPE_ALARM:
+ audibleContainer = mAlarmContainer;
+ playPause = mAlarmPlayPause;
+ component = MODIFIES_ALARMS;
+ break;
+ }
+ if (audibleContainer == null) return;
+ TextView label = (TextView) audibleContainer.findViewById(R.id.label);
+ label.setText(getAudibleLabel(type));
+
+ 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();
+ }
+ if (pkgNameIdx > -1) {
+ String pkgName = c.getString(pkgNameIdx);
+ try {
+ AudioUtils.loadThemeAudible(getActivity(), type, pkgName, mp);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Unable to load sound for " + pkgName, e);
+ return;
+ }
+ title.setText(c.getString(titleIdx));
+ mSelectedComponentsMap.put(component, pkgName);
+ } else {
+ final Context context = getActivity();
+ Uri ringtoneUri;
+ try {
+ ringtoneUri = AudioUtils.loadDefaultAudible(context, type, mp);
+ } catch (IOException e) {
+ Log.w(TAG, "Unable to load default sound ", e);
+ return;
+ }
+ title.setText(RingtoneManager.getRingtone(context, ringtoneUri).getTitle(context));
+ }
+
+ playPause.setTag(mp);
+ mMediaPlayers.put(playPause, mp);
+ playPause.setOnClickListener(mPlayPauseClickListener);
+ mp.setOnCompletionListener(mPlayCompletionListener);
+ }
+
+ private 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;
+ }
+
+ private 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;
+ }
+
+ 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;
+ }
+
+ 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 initCards(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 (mActiveCardId > 0) {
+ mActiveCardId = -1;
+ getActivity().onBackPressed();
+ return;
+ }
+ mActiveCardId = v.getId();
+ String component = mCardIdsToComponentTypes.get(mActiveCardId);
+ ((ChooserActivity) getActivity()).showComponentSelector(component, v);
+ fadeOutNonSelectedCards(mActiveCardId);
+ stopMediaPlayers();
+ }
+ };
+
+ private OnItemClickedListener mOnComponentItemClicked = new OnItemClickedListener() {
+ @Override
+ public void onItemClicked(String pkgName) {
+ Bundle args = new Bundle();
+ args.putString("pkgName", pkgName);
+ int loaderId = -1;
+ String component = mSelector.getComponentType();
+ 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)) {
+ loaderId = LOADER_ID_WALLPAPER;
+ } else if (MODIFIES_LOCKSCREEN.equals(component)) {
+ 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;
+ }
+ getLoaderManager().restartLoader(loaderId, args, ThemeFragment.this);
+ }
+ };
+
+ 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));
+ card.animateCardFadeOut();
+ }
+ }
+ }
+
+ private 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) {
+ if (mSelectedComponentsMap != null && mSelectedComponentsMap.size() > 0) {
+ if (!CURRENTLY_APPLIED_THEME.equals(mPkgName)) {
+ ThemeUtils.completeComponentMap(getActivity(),
+ mSelectedComponentsMap);
+ }
+ // Post this on mHandler so the client is added and removed from the same
+ // thread
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ ThemeManager tm = getThemeManager();
+ if (tm != null) {
+ // if this is not the "my theme" card, add missing components
+ // from defaults
+ tm.addClient(ThemeFragment.this);
+ tm.requestThemeChange(mSelectedComponentsMap);
+ }
+ }
+ });
+ }
+ }
+ }
+ };
+
+ private void applyTheme() {
+ if (mSelectedComponentsMap == null || mSelectedComponentsMap.size() <= 0) return;
+ ((ChooserActivity) getActivity()).themeChangeStarted();
+ animateProgressIn(mApplyThemeRunnable);
+ }
+
+ 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 / 4))
+ .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);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+ });
+
+ mTitleLayout.animate()
+ .translationXBy((pivotX / 4))
+ .alpha(1f)
+ .setDuration(ANIMATE_TITLE_IN_DURATION)
+ .setInterpolator(new AccelerateInterpolator())
+ .start();
+ mProgress.startAnimation(scaleAnim);
+ }
+
+ private void animateContentIn() {
+ 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();
+ }
+
+ public void fadeInCards() {
+ mActiveCardId = -1;
+ for (int i = 0; i < mCardIdsToComponentTypes.size(); i++) {
+ ComponentCardView card = (ComponentCardView) getView().findViewById(
+ mCardIdsToComponentTypes.keyAt(i));
+ card.animateCardFadeIn();
+ }
+ }
+
+ public boolean componentsChanged() {
+ for (String key : mSelectedComponentsMap.keySet()) {
+ if (!mPkgName.equals(mSelectedComponentsMap.get(key))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void clearChanges() {
+ mSelectedComponentsMap.clear();
+ getLoaderManager().restartLoader(LOADER_ID_ALL, null, ThemeFragment.this);
+ }
+
+ private void uninstallTheme() {
+ Uri packageURI = Uri.parse("package:" + mPkgName);
+ Intent uninstallIntent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI);
+ uninstallIntent.putExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, true);
+ startActivityForResult(uninstallIntent,
+ ChooserActivity.REQUEST_UNINSTALL);
+ }
+
+ public void onActivityResult (int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_UNINSTALL) {
+ try {
+ ApplicationInfo ainfo = getActivity()
+ .getPackageManager().getApplicationInfo(mPkgName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ mIsUninstalled = true;
+ }
+ }
+ }
+
+ public boolean isUninstalled() {
+ return mIsUninstalled;
+ }
+
+ public void setCurrentTheme(Map<String, String> currentTheme) {
+ mCurrentTheme = currentTheme;
+ }
+
+ class AnimationLoader extends AsyncTask<Void, Void, Boolean> {
+ Context mContext;
+ String mPkgName;
+ BootAniImageView mBootAnim;
+ boolean mAutoStart;
+
+ public AnimationLoader(Context context, String pkgName, BootAniImageView bootAnim) {
+ this(context, pkgName, bootAnim, false);
+ }
+
+ public AnimationLoader(Context context, String pkgName, BootAniImageView bootAnim,
+ boolean autoStart) {
+ mContext = context;
+ mPkgName = pkgName;
+ mBootAnim = bootAnim;
+ mAutoStart = autoStart;
+ }
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ }
+
+ @Override
+ protected Boolean doInBackground(Void... params) {
+ if (mContext == null) {
+ return Boolean.FALSE;
+ }
+ ZipFile zip = null;
+ if (ThemeConfig.HOLO_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 && mAutoStart) {
+ mBootAnim.start();
+ }
+ }
+ }
+}
diff --git a/src/com/cyngn/theme/chooser/WallpaperCardView.java b/src/com/cyngn/theme/chooser/WallpaperCardView.java
new file mode 100644
index 0000000..22d1a32
--- /dev/null
+++ b/src/com/cyngn/theme/chooser/WallpaperCardView.java
@@ -0,0 +1,115 @@
+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);
+ }
+
+ @Override
+ public void expand(boolean showLabel) {
+ // Do nothing
+ }
+
+ @Override
+ public void collapse() {
+ // Do nothing
+ }
+
+ /**
+ * 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/util/AudioUtils.java b/src/com/cyngn/theme/util/AudioUtils.java
new file mode 100644
index 0000000..d9621df
--- /dev/null
+++ b/src/com/cyngn/theme/util/AudioUtils.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2014 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 com.cyngn.theme.util;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ThemeUtils;
+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 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.HOLO_DEFAULT.equals(pkgName)) {
+ loadSystemAudible(type, mp);
+ return;
+ }
+ PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0);
+ Context themeCtx = context.createPackageContext(pkgName, 0);
+ if (pi.isLegacyThemeApk) {
+ loadLegacyThemeAudible(themeCtx, type, pi, mp);
+ return;
+ }
+ 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 loadLegacyThemeAudible(Context themeCtx, int type, PackageInfo pi,
+ MediaPlayer mp) {
+ if (pi.legacyThemeInfos == null || pi.legacyThemeInfos.length == 0)
+ return;
+ AssetManager assetManager = themeCtx.getAssets();
+ String assetPath;
+ switch (type) {
+ case RingtoneManager.TYPE_NOTIFICATION:
+ assetPath = pi.legacyThemeInfos[0].notificationFileName;
+ break;
+ case RingtoneManager.TYPE_RINGTONE:
+ assetPath = pi.legacyThemeInfos[0].ringtoneFileName;
+ break;
+ default:
+ assetPath = null;
+ break;
+ }
+ if (assetPath != null) {
+ try {
+ AssetFileDescriptor afd = assetManager.openFd(assetPath);
+ if (mp != null) {
+ mp.reset();
+ mp.setDataSource(afd.getFileDescriptor(),
+ afd.getStartOffset(), afd.getLength());
+ mp.prepare();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to load legacy sound for " + pi.packageName, 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);
+ 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
new file mode 100644
index 0000000..23ced7c
--- /dev/null
+++ b/src/com/cyngn/theme/util/BootAnimationHelper.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2014 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 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.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.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+public class BootAnimationHelper {
+ 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 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<String> 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<String>();
+ }
+
+ public void addFrame(String frame) {
+ frames.add(frame);
+ }
+ }
+
+ /**
+ * Gather up all the details for the given boot animation
+ * @param context
+ * @param zip The bootanimation.zip
+ * @return A list of AnimationPart if successful, null if not.
+ * @throws IOException
+ */
+ public static List<AnimationPart> parseAnimation(Context context, ZipFile zip)
+ throws IOException {
+ List<AnimationPart> animationParts = null;
+
+ ZipEntry ze = zip.getEntry("desc.txt");
+ if (ze != null) {
+ animationParts = parseDescription(zip.getInputStream(ze));
+ }
+
+ if (animationParts == null) return null;
+
+ for (AnimationPart a : animationParts) {
+ for (Enumeration<? extends ZipEntry> e = zip.entries();e.hasMoreElements();) {
+ ze = e.nextElement();
+ if (!ze.isDirectory() && ze.getName().contains(a.partName)) {
+ a.addFrame(ze.getName());
+ }
+ }
+ Collections.sort(a.frames);
+ }
+
+ 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<AnimationPart> parseDescription(InputStream in) throws IOException {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ String line = reader.readLine();
+ String[] details = line.split(" ");
+ final int width = Integer.parseInt(details[0]);
+ final int height = Integer.parseInt(details[1]);
+ final int frameRateMillis = 1000 / Integer.parseInt(details[2]);
+
+ List<AnimationPart> animationParts = new ArrayList<AnimationPart>();
+ while ((line = reader.readLine()) != null) {
+ String[] info = line.split(" ");
+ if (info.length == 4 && (info[0].equals("p") || info[0].equals("c"))) {
+ 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<Object, Void, Bitmap> {
+ 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.HOLO_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);
+ }
+ }
+ }
+}
diff --git a/src/com/cyngn/theme/util/FontConfigParser.java b/src/com/cyngn/theme/util/FontConfigParser.java
new file mode 100644
index 0000000..d88b4d4
--- /dev/null
+++ b/src/com/cyngn/theme/util/FontConfigParser.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2014 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 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:
+ *
+ *<familyset>
+ *
+ * <family>
+ * <nameset>
+ * <name>sans-serif</name>
+ * <name>arial</name>
+ * </nameset>
+ * <fileset>
+ * <file>Roboto-Regular.ttf</file>
+ * <file>Roboto-Bold.ttf</file>
+ * <file>Roboto-Italic.ttf</file>
+ * <file>Roboto-BoldItalic.ttf</file>
+ * </fileset>
+ * </family>
+ * <family>
+ * ...
+ * </family>
+ *</familyset>
+ */
+public class FontConfigParser {
+
+ public static class Family {
+ public List<String> nameset = new ArrayList<String>();
+ public List<String> fileset = new ArrayList<String>();
+ }
+
+ public static List<Family> 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<Family> readFamilySet(XmlPullParser parser) throws XmlPullParserException, IOException {
+ List<Family> families = new ArrayList<Family>();
+ 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<String> nameset = readNameset(parser);
+ family.nameset = nameset;
+ } else if (name.equals("fileset")) {
+ List<String> fileset = readFileset(parser);
+ family.fileset = fileset;
+ } else {
+ skip(parser);
+ }
+ }
+ return family;
+ }
+
+ private static List<String> readNameset(XmlPullParser parser) throws XmlPullParserException, IOException {
+ List<String> names = new ArrayList<String>();
+ 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<String> readFileset(XmlPullParser parser) throws XmlPullParserException, IOException {
+ List<String> files = new ArrayList<String>();
+ 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
new file mode 100644
index 0000000..a391c36
--- /dev/null
+++ b/src/com/cyngn/theme/util/IconPreviewHelper.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2014 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 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 != null || iconInfo.iconUpon != null)) {
+ 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 == null && iconInfo.iconUpon == null) {
+ return;
+ }
+
+ res.setComposedIconInfo(iconInfo);
+
+ SparseArray<PackageItemInfo> icons = new SparseArray<PackageItemInfo>(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, 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
new file mode 100644
index 0000000..bd022cd
--- /dev/null
+++ b/src/com/cyngn/theme/util/NotificationHelper.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 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 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.graphics.BitmapFactory;
+import android.text.TextUtils;
+
+import com.cyngn.theme.chooser.ChooserActivity;
+import com.cyngn.theme.chooser.R;
+
+public class NotificationHelper {
+ public static void postThemeInstalledNotification(Context context, String pkgName) {
+ String themeName = null;
+ try {
+ PackageInfo pi = context.getPackageManager().getPackageInfo(pkgName, 0);
+ if (pi.themeInfos != null && pi.themeInfos.length > 0) {
+ themeName = pi.themeInfos[0].name;
+ } else if (pi.legacyThemeInfos != null && pi.legacyThemeInfos[0] != null) {
+ themeName = pi.legacyThemeInfos[0].name;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ return;
+ }
+ if (TextUtils.isEmpty(themeName)) {
+ return;
+ }
+
+ 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);
+
+ NotificationManager nm =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ Notification notice = new Notification.Builder(context)
+ .setAutoCancel(true)
+ .setOngoing(false)
+ .setContentTitle(String.format(
+ context.getString(R.string.theme_installed_notification_title), themeName))
+ .setContentText(context.getString(R.string.theme_installed_notification_text))
+ .setContentIntent(pi)
+ .setSmallIcon(R.drawable.ic_notifiy)
+ .setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
+ R.drawable.ic_app_themes))
+ .setWhen(System.currentTimeMillis())
+ .build();
+ nm.notify(pkgName.hashCode(), notice);
+ }
+
+ public static void cancelNotificationForPackage(Context context, String pkgName) {
+ NotificationManager nm = (NotificationManager)
+ context.getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.cancel(pkgName.hashCode());
+ }
+}
diff --git a/src/com/cyngn/theme/util/ThemedTypefaceHelper.java b/src/com/cyngn/theme/util/ThemedTypefaceHelper.java
new file mode 100644
index 0000000..a46db90
--- /dev/null
+++ b/src/com/cyngn/theme/util/ThemedTypefaceHelper.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2014 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 com.cyngn.theme.util;
+
+import android.content.Context;
+import android.content.pm.ThemeUtils;
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.util.Log;
+
+import com.cyngn.theme.util.FontConfigParser.Family;
+
+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<Family> 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. Falling back to system fonts");
+ }
+
+ try {
+ loadSystemFonts();
+ return;
+ } catch(Exception e) {
+ Log.e(TAG, "Parsing system fonts failed. Falling back to Typeface loaded fonts");
+
+ }
+
+ // 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 + ThemeUtils.FONT_XML);
+ mFamilies = FontConfigParser.parse(is);
+
+ //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);
+ mFamilies = FontConfigParser.parse(is);
+
+ //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.nameset.contains(familyName)) {
+ return family;
+ }
+ }
+ throw new Exception("Unable to find " + familyName);
+ }
+
+ private Typeface loadTypeface(Family family, int style) {
+ AssetManager assets = mThemeContext.getAssets();
+ String path = FONTS_DIR + family.fileset.get(style);
+ return Typeface.createFromAsset(assets, path);
+ }
+
+ private Typeface loadSystemTypeface(Family family, int style) {
+ return Typeface.createFromFile(SYSTEM_FONTS_DIR + family.fileset.get(style));
+ }
+
+ 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
new file mode 100644
index 0000000..83661a6
--- /dev/null
+++ b/src/com/cyngn/theme/util/TypefaceHelperCache.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 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 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<String, ThemedTypefaceHelper> mCache;
+
+ private TypefaceHelperCache() {
+ mCache = new HashMap<String, ThemedTypefaceHelper>();
+ }
+
+ 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
new file mode 100644
index 0000000..a6314bc
--- /dev/null
+++ b/src/com/cyngn/theme/util/Utils.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2014 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 com.cyngn.theme.util;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Rect;
+import android.provider.ThemesContract;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ViewConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class Utils {
+ private static final String TAG = Utils.class.getSimpleName();
+
+ 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) {
+ return !ViewConfiguration.get(context).hasPermanentMenuKey();
+ }
+
+ public static Bitmap loadBitmapBlob(Cursor cursor, int columnIdx) {
+ if (columnIdx < 0) {
+ Log.w(TAG, "loadBitmapBlob(): Invalid index provided, returning null");
+ return null;
+ }
+ byte[] blob = cursor.getBlob(columnIdx);
+ if (blob == null) return null;
+ return BitmapFactory.decodeByteArray(blob, 0, blob.length);
+ }
+
+ 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;
+ }
+ }
+
+}
diff --git a/src/com/cyngn/theme/widget/BootAniImageView.java b/src/com/cyngn/theme/widget/BootAniImageView.java
new file mode 100644
index 0000000..7c599fc
--- /dev/null
+++ b/src/com/cyngn/theme/widget/BootAniImageView.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2014 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 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 int MAX_BUFFERS = 2;
+
+ private Bitmap[] mBuffers = new Bitmap[MAX_BUFFERS];
+ private int mReadBufferIndex = 0;
+ private int mWriteBufferIndex = 0;
+ private ZipFile mBootAniZip;
+
+ private List<BootAnimationHelper.AnimationPart> 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) {
+ // 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(mContext, mBootAniZip);
+ } catch (IOException e) {
+ 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() {
+ 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 (Exception 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() {
+ 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/ClickableViewPager.java b/src/com/cyngn/theme/widget/ClickableViewPager.java
new file mode 100644
index 0000000..9d76673
--- /dev/null
+++ b/src/com/cyngn/theme/widget/ClickableViewPager.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 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 com.cyngn.theme.widget;
+
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+/**
+ * Adds click functionality for the view pager since it not normally clickable
+ * and focus/clickable attributes have no impact on it.
+ */
+public class ClickableViewPager extends ViewPager {
+ private boolean mIsDragging = false;
+ private float mSlop;
+ private float mLastX;
+ private float mLastY;
+
+ public ClickableViewPager(Context context) {
+ super(context);
+ initView(context);
+ }
+
+ public ClickableViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initView(context);
+ }
+
+ private void initView(Context context) {
+ mSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mLastX = ev.getX();
+ mLastY = ev.getY();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ float xDist = Math.abs(mLastX - ev.getX());
+ float yDist = Math.abs(mLastY - ev.getY());
+ if (xDist > mSlop || yDist > mSlop) {
+ mIsDragging = true;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ mIsDragging = false;
+ break;
+ case MotionEvent.ACTION_UP:
+ if (!mIsDragging) {
+ performClick();
+ }
+ mIsDragging = false;
+ break;
+ }
+ return super.onTouchEvent(ev);
+ }
+} \ No newline at end of file
diff --git a/src/com/cyngn/theme/widget/FittedTextView.java b/src/com/cyngn/theme/widget/FittedTextView.java
new file mode 100644
index 0000000..efbe9be
--- /dev/null
+++ b/src/com/cyngn/theme/widget/FittedTextView.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2014 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 com.cyngn.theme.widget;
+
+import android.content.Context;
+import android.graphics.Paint;
+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;
+
+ public FittedTextView(Context context) {
+ super(context);
+ mPaint = new Paint();
+ }
+
+ public FittedTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mPaint = new Paint();
+ }
+
+ public FittedTextView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mPaint = new Paint();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ final float THRESHOLD = 0.5f;
+ final float TARGET_WIDTH = getMeasuredWidth();
+ final String text = getText().toString();
+ mPaint.set(getPaint());
+
+ 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);
+ }
+}