Java tutorial
/* * Copyright (c) 2014 Samuel Jarosiski <samueljarosinski@gmail.com> * * This file is part of "Tell me time". * http://tellmetime.info * * "Tell me time" is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * "Tell me time" is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with "Tell me time". If not, see <http://www.gnu.org/licenses/>. */ package info.tellmetime; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ArgbEvaluator; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; import android.app.WallpaperManager; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; import android.graphics.Shader; import android.graphics.Typeface; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RoundRectShape; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.provider.MediaStore; import android.support.v4.view.MotionEventCompat; import android.support.v7.widget.PopupMenu; import android.util.Property; import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RadioButton; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Calendar; import java.util.Timer; import java.util.TimerTask; public class TellmetimeActivity extends Activity implements View.OnTouchListener, SeekBar.OnSeekBarChangeListener, PopupMenu.OnMenuItemClickListener, AdapterView.OnItemSelectedListener { public static final String HIGHLIGHT = "highlight"; public static final String BACKLIGHT = "backlight"; public static final String BACKGROUND = "background"; public static final String BACKGROUND_MODE = "background_mode"; public static final String BACKGROUND_MODE_TOGGLED = "background_mode_toggled"; public static final String POSITION = "position"; public static final String MINUTES_SIZE = "minutes_size"; public static final String IMAGE_FILE_NAME = "image"; public static final String NIGHTMODE = "night_mode"; public static final int MODE_BACKGROUND_SOLID = 0; public static final int MODE_BACKGROUND_WALLPAPER = 1; public static final int MODE_BACKGROUND_IMAGE = 2; private static final int REQUEST_CODE = 1; /** * The {@link android.widget.RelativeLayout} which is the root layout of main activity. */ private RelativeLayout mSurface; /** * Background picture displaying either current wallpaper or user selected image. */ private ImageView mBackgroundImage; /** * The {@link android.widget.SeekBar} for adjusting highlight color. */ private SeekBar mSeekBarHighlight; /** * The instance of the {@link PanelHider} for this activity. */ private PanelHider mHider; /** * The shorter edge of the Activity window. */ private int mShorterEdge; /** * The density of the screen as scaling factor from 160dpi. */ private float mDensity; /** * The current highlight color used in the clock. */ private int mHighlightColor; /** * The current dim color used in the clock. */ private int mBacklightColor; /** * Current background mode; */ private int mBackgroundMode = MODE_BACKGROUND_SOLID; /** * The current background color represented as its Integer value. */ private int mBackgroundColor; /** * Cached background image. */ private Bitmap mImage; /** Array with cached Hue, Saturation and Value of background color. * {0..360, 0..1, 0..1} */ private final float[] mHSV = new float[3]; /** * Value indicating relative position of #mSeekBarHighlight. */ private float mHighlightPosition = 0.0f; /** * Size of minutes indicators in DP. */ private int mMinutesSize = 36; /** * Cached bitmap representing colors in #mSeekBarHighlight, used to retrieve color at #mSeekBarHighlight position. */ private Bitmap mRainbow; /** * Flag indicating whether screen is at full brightness or is dimmed. */ private boolean isNightMode; // Distance in pixels that needs to be exceeded to recognize an event as a actual moving // and not as just tapping the screen. // (Often touch interaction cause at least one MotionEvent.ACTION_MOVE signal). private int mTouchSlop; // Cached values. private int mScreenWidth; private int mScreenHeight; private int mBacklightLight; private int mBacklightDark; private SharedPreferences mSettings = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_tellmetime); mDensity = getResources().getDisplayMetrics().density; mScreenWidth = getResources().getDisplayMetrics().widthPixels; mScreenHeight = getResources().getDisplayMetrics().heightPixels; mShorterEdge = Math.min(mScreenWidth, mScreenHeight); mTouchSlop = ViewConfiguration.get(this).getScaledTouchSlop(); mBacklightLight = getResources().getColor(R.color.backlight_light); mBacklightDark = getResources().getColor(R.color.backlight_dark); // Restore background and highlight colors from saved values or set defaults. mSettings = getSharedPreferences("PREFS", Context.MODE_PRIVATE); mHighlightColor = mSettings.getInt(HIGHLIGHT, Color.WHITE); mBacklightColor = mSettings.getInt(BACKLIGHT, mBacklightLight); mBackgroundColor = mSettings.getInt(BACKGROUND, getResources().getColor(R.color.background)); mBackgroundMode = mSettings.getInt(BACKGROUND_MODE, MODE_BACKGROUND_SOLID); mHighlightPosition = mSettings.getFloat(POSITION, 0.0f); mMinutesSize = mSettings.getInt(MINUTES_SIZE, 36); isNightMode = mSettings.getBoolean(NIGHTMODE, false); // Dim the navigation bar. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) getWindow().getDecorView().setSystemUiVisibility(isNightMode ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0); mSurface = (RelativeLayout) findViewById(R.id.surface); mSurface.setBackgroundColor(mBackgroundColor); resizeClock(); Typeface mTypefaceBold = Typeface.createFromAsset(getAssets(), "Roboto-BoldCondensed.ttf"); // Set typeface of all items in the clock to Roboto and dim each one and drop shadow on them. final LinearLayout mClock = (LinearLayout) findViewById(R.id.clock); for (int i = 0; i < mClock.getChildCount(); i++) { LinearLayout row = (LinearLayout) mClock.getChildAt(i); for (int j = 0; j < row.getChildCount(); j++) { TextView tv = (TextView) row.getChildAt(j); tv.setTypeface(mTypefaceBold); tv.setTextColor(mBacklightColor); tv.setShadowLayer(mShorterEdge / 200 * mDensity, 0, 0, mBacklightColor); } } ViewGroup minutesDots = (ViewGroup) findViewById(R.id.minutes_dots); for (int i = 0; i < minutesDots.getChildCount(); i++) { TextView m = (TextView) minutesDots.getChildAt(i); m.setTypeface(mTypefaceBold); m.setTextColor(mBacklightColor); m.setShadowLayer(mShorterEdge / 200 * mDensity, 0, 0, mBacklightColor); } // Set Roboto font on TextView where it isn't default. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { Typeface mTypefaceItalic = Typeface.createFromAsset(getAssets(), "Roboto-CondensedItalic.ttf"); ((TextView) findViewById(R.id.labelBackground)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.info_background)).setTypeface(mTypefaceItalic); ((TextView) findViewById(R.id.info_image)).setTypeface(mTypefaceItalic); ((TextView) findViewById(R.id.labelHighlight)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.labelBackground)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.radio_backlight_light)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.radio_backlight_dark)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.radio_backlight_highlight)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.labelMinutes)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.m1)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.m2)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.m3)).setTypeface(mTypefaceBold); ((TextView) findViewById(R.id.m4)).setTypeface(mTypefaceBold); } FrameLayout mTouchZone = (FrameLayout) findViewById(R.id.touchZone); mTouchZone.setOnTouchListener(this); mTouchZone.setBackgroundColor( getResources().getColor(isNightMode ? R.color.night_mode_overlay : android.R.color.transparent)); mBackgroundImage = (ImageView) findViewById(R.id.background_image); switchBackgroundMode(mBackgroundMode); RelativeLayout mPanel = (RelativeLayout) findViewById(R.id.panel); mPanel.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { mHider.delayedHide(4000); return true; } }); mHider = new PanelHider(mPanel, this); Spinner spinnerBackgroundMode = (Spinner) findViewById(R.id.spinnerBackgroundMode); ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.backgrounds_modes, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinnerBackgroundMode.setAdapter(adapter); spinnerBackgroundMode.setOnItemSelectedListener(this); spinnerBackgroundMode.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { mHider.showNoAutoHide(); return false; } }); spinnerBackgroundMode.setSelection(mBackgroundMode); mSeekBarHighlight = (SeekBar) findViewById(R.id.highlightValue); mSeekBarHighlight.setOnSeekBarChangeListener(this); // Draw rainbow gradient on #mSeekBarHighlight and set position. drawRainbow(); if (mBacklightColor == mBacklightLight) ((RadioButton) findViewById(R.id.radio_backlight_light)).setChecked(true); else if (mBacklightColor == mBacklightDark) ((RadioButton) findViewById(R.id.radio_backlight_dark)).setChecked(true); else ((RadioButton) findViewById(R.id.radio_backlight_highlight)).setChecked(true); SeekBar mSeekBarMinutes = (SeekBar) findViewById(R.id.minutesSize); mSeekBarMinutes.setOnSeekBarChangeListener(this); mSeekBarMinutes.setProgress(mMinutesSize); mHider.hideNow(); Color.colorToHSV(mBackgroundColor, mHSV); mHSV[1] = 1.0f; //Trigger initial tick. mClockAlgorithm.tickTock(); // Schedule the clock algorithm to tick every round minute. Calendar time = Calendar.getInstance(); time.set(Calendar.MILLISECOND, 0); time.set(Calendar.SECOND, 0); time.add(Calendar.MINUTE, 1); Timer timer = new Timer(); timer.schedule(mClockTask, time.getTime(), 60 * 1000); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { mSurface.setAlpha(0.0f); mSurface.animate().alpha(1.0f).setDuration(1500); } // If it is first run, hint to user that panel is available. if (!mSettings.contains(HIGHLIGHT)) showToast(R.string.info_first_run); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { boolean enableTranslucentNavigation = true; if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE && newConfig.smallestScreenWidthDp < 600) enableTranslucentNavigation = false; if (enableTranslucentNavigation) getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); else getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); } mScreenWidth = getResources().getDisplayMetrics().widthPixels; mScreenHeight = getResources().getDisplayMetrics().heightPixels; mShorterEdge = Math.min(mScreenWidth, mScreenHeight); resizeClock(); mClockAlgorithm.tickTock(); } /** * Called when Activity is about to lose focus, so most likely the user is leaving. */ @Override protected void onPause() { super.onPause(); // Send current highlight color to ClockService, so widget can be updated with new color. Intent intent = new Intent(getApplicationContext(), ClockService.class); startService(intent); SharedPreferences.Editor editor = mSettings.edit(); editor.putInt(BACKGROUND, mBackgroundColor); editor.putInt(HIGHLIGHT, mHighlightColor); editor.putInt(BACKLIGHT, mBacklightColor); editor.putInt(BACKGROUND_MODE, mBackgroundMode); editor.putFloat(POSITION, mHighlightPosition); editor.putInt(MINUTES_SIZE, mMinutesSize); editor.putBoolean(NIGHTMODE, isNightMode); editor.commit(); } // Cached coordinates of the starting point of new touch gesture, i.e. when the user // touched the screen and started to move or is just tapping the screen. float mStartX = 0, mStartY = 0; // Flag that is set if the touch event was recognized as a movement rather than just a tap. boolean isSlopped = false; // Flag indicating whether day/night mode was toggled. If set, panel visibility shouldn't be toggled. boolean wasModeToggled = false; // Handler responsible for long pressing. final Handler mLongPressHandler = new Handler(); Runnable mLongPressed = new Runnable() { public void run() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) toggleMode(); } }; /** * Processes touch events for FrameLayout overlaying whole activity. * * If the touch was not on the panel then it changes background color according to the touch * position as long as the touch event was recognized as a movement, * or toggles panel visibility if it was a tap. * * @return true if the touch event was consumed. */ @Override public boolean onTouch(View view, MotionEvent event) { // Coordinates of current touch point. final float rawX = event.getRawX(); final float rawY = event.getRawY(); switch (MotionEventCompat.getActionMasked(event)) { case (MotionEvent.ACTION_DOWN): // This action signals that a new touch gesture begins, so we cache its starting point. mStartX = rawX; mStartY = rawY; // Schedule a Runnable to run in 1 second. mLongPressHandler.postDelayed(mLongPressed, 1000); return true; case (MotionEvent.ACTION_MOVE): // Distance between starting point and current one. final double distance = Math.sqrt(Math.pow(rawX - mStartX, 2) + Math.pow(rawY - mStartY, 2)); if (distance > mTouchSlop) { // If the user exceeded the slop value, then we start to track the movement. isSlopped = true; // Cancel scheduled Runnable. mLongPressHandler.removeCallbacks(mLongPressed); if (mHider.isVisible()) { // Hide panel to present only the clock. mHider.hide(); } else { if (mBackgroundMode != MODE_BACKGROUND_SOLID) return true; // Change the background color according to the touch position. if (event.getPointerCount() > 1) { // If the user is using more than one finger, then we change saturation. // Saturation depends on top <-> bottom movement of second finger. mHSV[1] = MotionEventCompat.getY(event, 1) / mScreenHeight; } else { // If it's a single finger gesture, then we change hue and value. // Hue depends on left <-> right movement. mHSV[0] = (float) Math.floor(rawX / mScreenWidth * 360); // Value depends on up <-> down movement. mHSV[2] = rawY / mScreenHeight; } mBackgroundColor = Color.HSVToColor(mHSV); mSurface.setBackgroundColor(mBackgroundColor); } } else { // If required distance is not exceeded, we only unset the slop flag. isSlopped = false; } return true; case (MotionEvent.ACTION_UP): mLongPressHandler.removeCallbacks(mLongPressed); // This action ends every touch interaction, so if the event was just a tap, // i.e. distance hasn't exceed the slop value, then it concerns panel. if (!isSlopped && !wasModeToggled) { // If the touch gesture was just a tap then toggle panel visibility. mHider.toggle(); } // As the gesture ended, we need to unset the slop and toggle flag. isSlopped = false; wasModeToggled = false; return true; case (MotionEvent.ACTION_POINTER_UP): // Action occurs when second or farther finger goes up. isSlopped = true; return true; default: return super.onTouchEvent(event); } } @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { // Delay hiding the panel. This is to prevent the jarring behavior of // controls going away while interacting with UI. mHider.delayedHide(4000); FrameLayout frameBackgroundMode = (FrameLayout) findViewById(R.id.frameBackgroundMode); for (int i = 0; i < frameBackgroundMode.getChildCount(); i++) frameBackgroundMode.getChildAt(i).setVisibility(View.GONE); frameBackgroundMode.getChildAt(position).setVisibility(View.VISIBLE); mBackgroundMode = position; switchBackgroundMode(position); } @Override public void onNothingSelected(AdapterView<?> parent) { } /** * Handles changing highlight color via #mSeekBarHighlight. * * @param value indicates exact offset in gradient (SeekBar's max value is equal to #mRainbow width). */ @Override public void onProgressChanged(SeekBar seekBar, int value, boolean fromUser) { switch (seekBar.getId()) { case R.id.highlightValue: mHighlightColor = mRainbow.getPixel(value, 0); if (((RadioButton) findViewById(R.id.radio_backlight_highlight)).isChecked()) { float[] highlightHSV = new float[3]; Color.colorToHSV(mHighlightColor, highlightHSV); mBacklightColor = Color.HSVToColor(33, highlightHSV); } if (fromUser) mHighlightPosition = (float) value / seekBar.getMax(); mClockAlgorithm.tickTock(); break; case R.id.minutesSize: mMinutesSize = value; if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { FrameLayout minutesIndicators = (FrameLayout) findViewById(R.id.minutes_indicators); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) minutesIndicators .getLayoutParams(); params.topMargin = (int) -TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mMinutesSize / 3, getResources().getDisplayMetrics()); minutesIndicators.setLayoutParams(params); } ViewGroup minutesDots = (ViewGroup) findViewById(R.id.minutes_dots); for (int i = 0; i < minutesDots.getChildCount(); i++) { TextView m = (TextView) minutesDots.getChildAt(i); m.setTextSize(TypedValue.COMPLEX_UNIT_DIP, mMinutesSize); } break; } mHider.delayedHide(4000); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } public void onBacklightRadioClick(View view) { boolean isChecked = ((RadioButton) view).isChecked(); switch (view.getId()) { case R.id.radio_backlight_light: if (isChecked) mBacklightColor = mBacklightLight; break; case R.id.radio_backlight_dark: if (isChecked) mBacklightColor = mBacklightDark; break; case R.id.radio_backlight_highlight: if (isChecked) { float[] highlightHSV = new float[3]; Color.colorToHSV(mHighlightColor, highlightHSV); mBacklightColor = Color.HSVToColor(33, highlightHSV); } break; } mClockAlgorithm.tickTock(); mHider.delayedHide(4000); } public void showPopupMenu(View view) { PopupMenu popup = new PopupMenu(this, view); popup.setOnMenuItemClickListener(this); popup.setOnDismissListener(new PopupMenu.OnDismissListener() { @Override public void onDismiss(PopupMenu popupMenu) { mHider.delayedHide(4000); } }); popup.getMenuInflater().inflate(R.menu.menu, popup.getMenu()); MenuItem toggle = popup.getMenu().findItem(R.id.action_mode); toggle.setTitle(isNightMode ? R.string.action_mode_day : R.string.action_mode_night); toggle.setIcon(isNightMode ? R.drawable.ic_action_brightness_high : R.drawable.ic_action_brightness_low); popup.show(); mHider.showNoAutoHide(); } @Override public boolean onMenuItemClick(MenuItem menuItem) { switch (menuItem.getItemId()) { case R.id.action_mode: if (!mSettings.contains(BACKGROUND_MODE_TOGGLED)) showToast(R.string.info_toggle_mode); toggleMode(); mHider.hide(); return true; case R.id.action_share_screenshot: mHider.hideNow(); new Thread(new Runnable() { @Override public void run() { try { View rootView = getWindow().getDecorView().getRootView(); rootView.setDrawingCacheEnabled(true); Bitmap screenshot = Bitmap.createBitmap(rootView.getDrawingCache()); rootView.setDrawingCacheEnabled(false); showToast(R.string.info_saving_screenshot); ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.TITLE, getString(R.string.app_name)); values.put(MediaStore.Images.Media.DISPLAY_NAME, getString(R.string.app_name) + ".jpg"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/*"); Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); OutputStream outputStream; outputStream = getContentResolver().openOutputStream(uri); screenshot.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); outputStream.close(); screenshot.recycle(); Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("image/jpeg"); intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.app_name)); String shareText = getString(R.string.app_description) + " Get it at http://tellmetime.info"; intent.putExtra(Intent.EXTRA_TEXT, shareText); intent.putExtra(Intent.EXTRA_STREAM, uri); startActivity(Intent.createChooser(intent, "Share via")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }).start(); return true; case R.id.action_share_app: try { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.app_name)); String shareText = getString(R.string.app_description) + " Get it at http://tellmetime.info"; intent.putExtra(Intent.EXTRA_TEXT, shareText); startActivity(Intent.createChooser(intent, "Share via")); } catch (Exception ignored) { } return true; case R.id.action_rate: Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("market://details?id=info.tellmetime")); startActivity(intent); return true; default: return false; } } Clock mClockAlgorithm = new Clock(true) { /** * Function that needs to provide setting color of the given TextView. * * @param item String with the TextView's name. * @param isHighlighting Indicates whether the function should highlight or dim the given item. */ @Override @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) protected void setColor(final String item, final boolean isHighlighting) { final int newColor = isHighlighting ? mHighlightColor : mBacklightColor; final int shadowSize = Math .min((isHighlighting ? mShorterEdge / 100 : mShorterEdge / 200) * (int) mDensity, 25); // Since messing with UI is possible only on the main thread (i.e. UI thread) // and clock is ticking in separated thread, we need to set color in UI thread. runOnUiThread(new Runnable() { @Override public void run() { final TextView tv = (TextView) findViewById(getItem(item)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { final Property<TextView, Integer> propertyHighlight = new Property<TextView, Integer>( int.class, "textColor") { @Override public Integer get(TextView object) { return object.getCurrentTextColor(); } @Override public void set(TextView object, Integer value) { object.setTextColor(value); object.setShadowLayer(shadowSize, 0, 0, value); } }; tv.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator animator = ObjectAnimator.ofInt(tv, propertyHighlight, newColor); animator.setDuration(1500L); animator.setEvaluator(new ArgbEvaluator()); animator.setInterpolator(new AccelerateDecelerateInterpolator()); animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { tv.setLayerType(View.LAYER_TYPE_NONE, null); } }); animator.start(); } else { tv.setTextColor(newColor); tv.setShadowLayer(shadowSize, 0, 0, newColor); } } }); } }; TimerTask mClockTask = new TimerTask() { @Override public void run() { mClockAlgorithm.tickTock(); } }; private void switchBackgroundMode(int position) { mBackgroundImage.setBackgroundResource(0); switch (position) { case MODE_BACKGROUND_SOLID: mBackgroundImage.setVisibility(View.GONE); break; case MODE_BACKGROUND_WALLPAPER: mBackgroundImage.setVisibility(View.VISIBLE); mBackgroundImage.setImageDrawable(WallpaperManager.getInstance(this).getDrawable()); break; case MODE_BACKGROUND_IMAGE: if (mImage == null) { try { File f = new File(getFilesDir(), IMAGE_FILE_NAME); mImage = BitmapFactory.decodeStream(new FileInputStream(f)); } catch (FileNotFoundException e) { pickImage(null); } } mBackgroundImage.setVisibility(View.VISIBLE); mBackgroundImage.setImageBitmap(mImage); break; } } public void pickImage(View view) { Intent intent = new Intent(); intent.setType("image/*"); intent.setAction(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(intent, REQUEST_CODE); } @Override protected void onActivityResult(int requestCode, int resultCode, final Intent data) { if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) { new Thread(new Runnable() { public void run() { try { InputStream stream = getContentResolver().openInputStream(data.getData()); // First decode with inJustDecodeBounds=true to check dimensions. BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(stream, null, options); // Calculate inSampleSize. // Raw height and width of image. final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > mShorterEdge || width > mShorterEdge) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the shorter screen edge. while ((halfHeight / inSampleSize) > mShorterEdge && (halfWidth / inSampleSize) > mShorterEdge) inSampleSize *= 2; } options.inSampleSize = inSampleSize; // Decode bitmap with inSampleSize set. stream = getContentResolver().openInputStream(data.getData()); options.inJustDecodeBounds = false; mImage = BitmapFactory.decodeStream(stream, null, options); mBackgroundImage.post(new Runnable() { public void run() { mBackgroundImage.setImageBitmap(mImage); } }); stream.close(); FileOutputStream outputStream = openFileOutput(IMAGE_FILE_NAME, Context.MODE_PRIVATE); mImage.compress(Bitmap.CompressFormat.PNG, 100, outputStream); outputStream.close(); } catch (FileNotFoundException e) { showToast(R.string.error_exception_file_not_found); e.printStackTrace(); } catch (IOException e) { showToast(R.string.error_exception_io); e.printStackTrace(); } catch (SecurityException e) { showToast(R.string.error_exception_security); e.printStackTrace(); } catch (Exception e) { showToast(R.string.error_exception); e.printStackTrace(); } } }).start(); } super.onActivityResult(requestCode, resultCode, data); } private void resizeClock() { final LinearLayout mClock = (LinearLayout) findViewById(R.id.clock); // Set width of #mClock layout to the screen's shorter edge size, so clock is not // expanded in landscape mode, but has rather somewhat a square shape. RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); lp.addRule(RelativeLayout.CENTER_IN_PARENT); lp.width = mShorterEdge; mClock.setLayoutParams(lp); final float mItemSize = mShorterEdge / mClock.getChildCount(); final int mRowMargin = (int) -(mItemSize / 2.2); // Scale text size according to shorter edge and set spacing between rows. for (int i = 0; i < mClock.getChildCount(); i++) { LinearLayout row = (LinearLayout) mClock.getChildAt(i); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) row.getLayoutParams(); params.bottomMargin = mRowMargin; row.setLayoutParams(params); for (int j = 0; j < row.getChildCount(); j++) ((TextView) row.getChildAt(j)).setTextSize(TypedValue.COMPLEX_UNIT_PX, mItemSize); } LinearLayout lastRow = (LinearLayout) mClock.getChildAt(mClock.getChildCount() - 1); LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) lastRow.getLayoutParams(); params.bottomMargin = 0; lastRow.setLayoutParams(params); TextView twenty = (TextView) findViewById(R.id.twenty); params = (LinearLayout.LayoutParams) twenty.getLayoutParams(); params.leftMargin = 0; twenty.setLayoutParams(params); // Inflates minutes indicators and attaches them to main view. FrameLayout minutesLayout = (FrameLayout) findViewById(R.id.minutes_indicators); minutesLayout.removeAllViews(); LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); final boolean isLandscape = getResources() .getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE; inflater.inflate(isLandscape ? R.layout.minutes_land : R.layout.minutes_portrait, minutesLayout); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, isLandscape ? FrameLayout.LayoutParams.MATCH_PARENT : FrameLayout.LayoutParams.WRAP_CONTENT); if (!isLandscape) { layoutParams.addRule(RelativeLayout.BELOW, R.id.clock); layoutParams.topMargin = (int) -TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mMinutesSize / 3, getResources().getDisplayMetrics()); } minutesLayout.setLayoutParams(layoutParams); ViewGroup minutesDots = (ViewGroup) findViewById(R.id.minutes_dots); for (int i = 0; i < minutesDots.getChildCount(); i++) ((TextView) minutesDots.getChildAt(i)).setTextSize(TypedValue.COMPLEX_UNIT_DIP, mMinutesSize); } /** * Generates rainbow gradient and sets it as #mSeekBarHighlight progress drawable. */ private void drawRainbow() { float gradientWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 256, getResources().getDisplayMetrics()); int[] colors = { 0xFFFFFFFF, 0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, 0xFF0000FF, 0xFFFF00FF, 0xFF888888, 0xFF000000 }; LinearGradient rainbowGradient = new LinearGradient(0f, 0f, gradientWidth, 0f, colors, null, Shader.TileMode.CLAMP); float r = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics()); ShapeDrawable shape = new ShapeDrawable( new RoundRectShape(new float[] { r, r, r, r, r, r, r, r }, null, null)); shape.getPaint().setShader(rainbowGradient); mSeekBarHighlight.setProgressDrawable(shape); // Generate bitmap with the same rainbow gradient and cache it for later use to retrieve color at given position. Bitmap bitmap = Bitmap.createBitmap((int) gradientWidth, 1, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStyle(Paint.Style.FILL); paint.setShader(rainbowGradient); canvas.drawRect(0, 0, gradientWidth, 1, paint); mRainbow = bitmap; // Set max value to gradient's width and restore relative position. mSeekBarHighlight.setMax((int) (gradientWidth - 1)); mSeekBarHighlight.setProgress((int) ((gradientWidth - 1) * mHighlightPosition)); } private void toggleMode() { isNightMode = !isNightMode; wasModeToggled = true; // Prevents toggling panels on ACTION_UP after long click. findViewById(R.id.touchZone).setBackgroundColor( getResources().getColor(isNightMode ? R.color.night_mode_overlay : android.R.color.transparent)); mSettings.edit().putBoolean(BACKGROUND_MODE_TOGGLED, true).commit(); } public void showToast(final int resID) { runOnUiThread(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), resID, Toast.LENGTH_LONG).show(); } }); } }