diff options
author | lado <herrlado@gmail.com> | 2015-06-09 14:07:14 +0200 |
---|---|---|
committer | lado <herrlado@gmail.com> | 2015-06-09 14:07:14 +0200 |
commit | 23d920c759303602c2a6cde38983b2aa4dbb82c2 (patch) | |
tree | d0d83c36e0698402c783ebf62e6fe7d8ee85bed2 /vdrmanager/app/src/main/java | |
parent | be5e0132d51a829326478c668e4eac78e2c9e4d8 (diff) | |
download | vdr-manager-23d920c759303602c2a6cde38983b2aa4dbb82c2.tar.gz vdr-manager-23d920c759303602c2a6cde38983b2aa4dbb82c2.tar.bz2 |
StudioStructur
Diffstat (limited to 'vdrmanager/app/src/main/java')
144 files changed, 22254 insertions, 0 deletions
diff --git a/vdrmanager/app/src/main/java/com/viewpagerindicator/CirclePageIndicator.java b/vdrmanager/app/src/main/java/com/viewpagerindicator/CirclePageIndicator.java new file mode 100644 index 0000000..901d45f --- /dev/null +++ b/vdrmanager/app/src/main/java/com/viewpagerindicator/CirclePageIndicator.java @@ -0,0 +1,437 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * Copyright (C) 2011 Jake Wharton + * + * 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.viewpagerindicator; + +import de.bjusystems.vdrmanager.R; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +/** + * Draws circles (one for each view). The current view position is filled and + * others are only stroked. + */ +public class CirclePageIndicator extends View implements PageIndicator { + public static final int HORIZONTAL = 0; + public static final int VERTICAL = 1; + + private float mRadius; + private final Paint mPaintStroke; + private final Paint mPaintFill; + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mListener; + private int mCurrentPage; + private int mSnapPage; + private int mCurrentOffset; + private int mScrollState; + private int mPageSize; + private int mOrientation; + private boolean mCentered; + private boolean mSnap; + + + public CirclePageIndicator(Context context) { + this(context, null); + } + + public CirclePageIndicator(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.circlePageIndicatorStyle); + } + + public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + //Load defaults from resources + final Resources res = getResources(); + final int defaultFillColor = res.getColor(R.color.default_circle_indicator_fill_color); + final int defaultOrientation = res.getInteger(R.integer.default_circle_indicator_orientation); + final int defaultStrokeColor = res.getColor(R.color.default_circle_indicator_stroke_color); + final float defaultStrokeWidth = res.getDimension(R.dimen.default_circle_indicator_stroke_width); + final float defaultRadius = res.getDimension(R.dimen.default_circle_indicator_radius); + final boolean defaultCentered = res.getBoolean(R.bool.default_circle_indicator_centered); + final boolean defaultSnap = res.getBoolean(R.bool.default_circle_indicator_snap); + + //Retrieve styles attributes + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePageIndicator, defStyle, R.style.Widget_CirclePageIndicator); + + mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered); + mOrientation = a.getInt(R.styleable.CirclePageIndicator_orientation, defaultOrientation); + mPaintStroke = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintStroke.setStyle(Style.STROKE); + mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor)); + mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth)); + mPaintFill = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaintFill.setStyle(Style.FILL); + mPaintFill.setColor(a.getColor(R.styleable.CirclePageIndicator_fillColor, defaultFillColor)); + mRadius = a.getDimension(R.styleable.CirclePageIndicator_radius, defaultRadius); + mSnap = a.getBoolean(R.styleable.CirclePageIndicator_snap, defaultSnap); + + a.recycle(); + } + + + public void setCentered(boolean centered) { + mCentered = centered; + invalidate(); + } + + public boolean isCentered() { + return mCentered; + } + + public void setFillColor(int fillColor) { + mPaintFill.setColor(fillColor); + invalidate(); + } + + public int getFillColor() { + return mPaintFill.getColor(); + } + + public void setOrientation(int orientation) { + switch (orientation) { + case HORIZONTAL: + case VERTICAL: + mOrientation = orientation; + updatePageSize(); + requestLayout(); + break; + + default: + throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL."); + } + } + + public int getOrientation() { + return mOrientation; + } + + public void setStrokeColor(int strokeColor) { + mPaintStroke.setColor(strokeColor); + invalidate(); + } + + public int getStrokeColor() { + return mPaintStroke.getColor(); + } + + public void setStrokeWidth(float strokeWidth) { + mPaintStroke.setStrokeWidth(strokeWidth); + invalidate(); + } + + public float getStrokeWidth() { + return mPaintStroke.getStrokeWidth(); + } + + public void setRadius(float radius) { + mRadius = radius; + invalidate(); + } + + public float getRadius() { + return mRadius; + } + + public void setSnap(boolean snap) { + mSnap = snap; + invalidate(); + } + + public boolean isSnap() { + return mSnap; + } + + /* + * (non-Javadoc) + * + * @see android.view.View#onDraw(android.graphics.Canvas) + */ + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + int longSize; + int longPaddingBefore; + int longPaddingAfter; + int shortPaddingBefore; + if (mOrientation == HORIZONTAL) { + longSize = getWidth(); + longPaddingBefore = getPaddingLeft(); + longPaddingAfter = getPaddingRight(); + shortPaddingBefore = getPaddingTop(); + } else { + longSize = getHeight(); + longPaddingBefore = getPaddingTop(); + longPaddingAfter = getPaddingBottom(); + shortPaddingBefore = getPaddingLeft(); + } + + final int count = mViewPager.getAdapter().getCount(); + final float threeRadius = mRadius * 3; + final float shortOffset = shortPaddingBefore + mRadius; + float longOffset = longPaddingBefore + mRadius; + if (mCentered) { + longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f); + } + + float dX; + float dY; + + //Draw stroked circles + for (int iLoop = 0; iLoop < count; iLoop++) { + float drawLong = longOffset + (iLoop * threeRadius); + if (mOrientation == HORIZONTAL) { + dX = drawLong; + dY = shortOffset; + } else { + dX = shortOffset; + dY = drawLong; + } + canvas.drawCircle(dX, dY, mRadius, mPaintStroke); + } + + //Draw the filled circle according to the current scroll + float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius; + if (!mSnap && (mPageSize != 0)) { + cx += (mCurrentOffset * 1.0f / mPageSize) * threeRadius; + } + if (mOrientation == HORIZONTAL) { + dX = longOffset + cx; + dY = shortOffset; + } else { + dX = shortOffset; + dY = longOffset + cx; + } + canvas.drawCircle(dX, dY, mRadius, mPaintFill); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + final int count = mViewPager.getAdapter().getCount(); + final int longSize = (mOrientation == HORIZONTAL) ? getWidth() : getHeight(); + final float halfLongSize = longSize / 2; + final float halfCircleLongSize = (count * 3 * mRadius) / 2; + final float pointerValue = (mOrientation == HORIZONTAL) ? event.getX() : event.getY(); + + if ((mCurrentPage > 0) && (pointerValue < halfLongSize - halfCircleLongSize)) { + setCurrentItem(mCurrentPage - 1); + return true; + } else if ((mCurrentPage < count - 1) && (pointerValue > halfLongSize + halfCircleLongSize)) { + setCurrentItem(mCurrentPage + 1); + return true; + } + } + + return super.onTouchEvent(event); + } + + public void setViewPager(ViewPager view) { + if (view.getAdapter() == null) { + throw new IllegalStateException("ViewPager does not have adapter instance."); + } + mViewPager = view; + mViewPager.setOnPageChangeListener(this); + updatePageSize(); + invalidate(); + } + + private void updatePageSize() { + if (mViewPager != null) { + mPageSize = (mOrientation == HORIZONTAL) ? mViewPager.getWidth() : mViewPager.getHeight(); + } + } + + public void setViewPager(ViewPager view, int initialPosition) { + setViewPager(view); + setCurrentItem(initialPosition); + } + + + public void setCurrentItem(int item) { + if (mViewPager == null) { + throw new IllegalStateException("ViewPager has not been bound."); + } + mViewPager.setCurrentItem(item); + mCurrentPage = item; + invalidate(); + } + + + public void onPageScrollStateChanged(int state) { + mScrollState = state; + + if (mListener != null) { + mListener.onPageScrollStateChanged(state); + } + } + + + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + mCurrentPage = position; + mCurrentOffset = positionOffsetPixels; + updatePageSize(); + invalidate(); + + if (mListener != null) { + mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + } + + + public void onPageSelected(int position) { + if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) { + mCurrentPage = position; + mSnapPage = position; + invalidate(); + } + + if (mListener != null) { + mListener.onPageSelected(position); + } + } + + + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mListener = listener; + } + + /* + * (non-Javadoc) + * + * @see android.view.View#onMeasure(int, int) + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (mOrientation == HORIZONTAL) { + setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec)); + } else { + setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec)); + } + } + + /** + * Determines the width of this view + * + * @param measureSpec + * A measureSpec packed into an int + * @return The width of the view, honoring constraints from measureSpec + */ + private int measureLong(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + //We were told how big to be + result = specSize; + } else { + //Calculate the width according the views count + final int count = mViewPager.getAdapter().getCount(); + result = (int)(getPaddingLeft() + getPaddingRight() + + (count * 2 * mRadius) + (count - 1) * mRadius + 1); + //Respect AT_MOST value if that was what is called for by measureSpec + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + return result; + } + + /** + * Determines the height of this view + * + * @param measureSpec + * A measureSpec packed into an int + * @return The height of the view, honoring constraints from measureSpec + */ + private int measureShort(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + //We were told how big to be + result = specSize; + } else { + //Measure the height + result = (int)(2 * mRadius + getPaddingTop() + getPaddingBottom() + 1); + //Respect AT_MOST value if that was what is called for by measureSpec + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + return result; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState)state; + super.onRestoreInstanceState(savedState.getSuperState()); + mCurrentPage = savedState.currentPage; + mSnapPage = savedState.currentPage; + requestLayout(); + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState savedState = new SavedState(superState); + savedState.currentPage = mCurrentPage; + return savedState; + } + + static class SavedState extends BaseSavedState { + int currentPage; + + public SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + currentPage = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(currentPage); + } + + public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/vdrmanager/app/src/main/java/com/viewpagerindicator/PageIndicator.java b/vdrmanager/app/src/main/java/com/viewpagerindicator/PageIndicator.java new file mode 100644 index 0000000..64786d2 --- /dev/null +++ b/vdrmanager/app/src/main/java/com/viewpagerindicator/PageIndicator.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * Copyright (C) 2011 Jake Wharton + * + * 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.viewpagerindicator; + +import android.support.v4.view.ViewPager; + +/** + * A PageIndicator is responsible to show an visual indicator on the total views + * number and the current visible view. + */ +public interface PageIndicator extends ViewPager.OnPageChangeListener { + /** + * Bind the indicator to a ViewPager. + * + * @param view + */ + public void setViewPager(ViewPager view); + + /** + * Bind the indicator to a ViewPager. + * + * @param view + * @param initialPosition + */ + public void setViewPager(ViewPager view, int initialPosition); + + /** + * <p>Set the current page of both the ViewPager and indicator.</p> + * + * <p>This <strong>must</strong> be used if you need to set the page before + * the views are drawn on screen (e.g., default start page).</p> + * + * @param item + */ + public void setCurrentItem(int item); + + /** + * Set a page change listener which will receive forwarded events. + * + * @param listener + */ + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener); +} diff --git a/vdrmanager/app/src/main/java/com/viewpagerindicator/TitlePageIndicator.java b/vdrmanager/app/src/main/java/com/viewpagerindicator/TitlePageIndicator.java new file mode 100644 index 0000000..c04f5ed --- /dev/null +++ b/vdrmanager/app/src/main/java/com/viewpagerindicator/TitlePageIndicator.java @@ -0,0 +1,672 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * Copyright (C) 2011 Francisco Figueiredo Jr. + * Copyright (C) 2011 Jake Wharton + * + * 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.viewpagerindicator; + +import java.util.ArrayList; + +import de.bjusystems.vdrmanager.R; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v4.view.ViewPager; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +/** + * A TitlePageIndicator is a PageIndicator which displays the title of left view + * (if exist), the title of the current select view (centered) and the title of + * the right view (if exist). When the user scrolls the ViewPager then titles are + * also scrolled. + */ +public class TitlePageIndicator extends View implements PageIndicator { + /** + * Percentage indicating what percentage of the screen width away from + * center should the underline be fully faded. A value of 0.25 means that + * halfway between the center of the screen and an edge. + */ + private static final float SELECTION_FADE_PERCENTAGE = 0.25f; + + /** + * Percentage indicating what percentage of the screen width away from + * center should the selected text bold turn off. A value of 0.05 means + * that 10% between the center and an edge. + */ + private static final float BOLD_FADE_PERCENTAGE = 0.05f; + + public enum IndicatorStyle { + None(0), Triangle(1), Underline(2); + + public final int value; + + private IndicatorStyle(int value) { + this.value = value; + } + + public static IndicatorStyle fromValue(int value) { + for (IndicatorStyle style : IndicatorStyle.values()) { + if (style.value == value) { + return style; + } + } + return null; + } + } + + private ViewPager mViewPager; + private ViewPager.OnPageChangeListener mListener; + private TitleProvider mTitleProvider; + private int mCurrentPage; + private int mCurrentOffset; + private int mScrollState; + private final Paint mPaintText; + private boolean mBoldText; + private int mColorText; + private int mColorSelected; + private Path mPath; + private final Paint mPaintFooterLine; + private IndicatorStyle mFooterIndicatorStyle; + private final Paint mPaintFooterIndicator; + private float mFooterIndicatorHeight; + private float mFooterIndicatorUnderlinePadding; + private float mFooterPadding; + private float mTitlePadding; + private float mTopPadding; + /** Left and right side padding for not active view titles. */ + private float mClipPadding; + private float mFooterLineHeight; + + + public TitlePageIndicator(Context context) { + this(context, null); + } + + public TitlePageIndicator(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.titlePageIndicatorStyle); + } + + public TitlePageIndicator(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + //Load defaults from resources + final Resources res = getResources(); + final int defaultFooterColor = res.getColor(R.color.default_title_indicator_footer_color); + final float defaultFooterLineHeight = res.getDimension(R.dimen.default_title_indicator_footer_line_height); + final int defaultFooterIndicatorStyle = res.getInteger(R.integer.default_title_indicator_footer_indicator_style); + final float defaultFooterIndicatorHeight = res.getDimension(R.dimen.default_title_indicator_footer_indicator_height); + final float defaultFooterIndicatorUnderlinePadding = res.getDimension(R.dimen.default_title_indicator_footer_indicator_underline_padding); + final float defaultFooterPadding = res.getDimension(R.dimen.default_title_indicator_footer_padding); + final int defaultSelectedColor = res.getColor(R.color.default_title_indicator_selected_color); + final boolean defaultSelectedBold = res.getBoolean(R.bool.default_title_indicator_selected_bold); + final int defaultTextColor = res.getColor(R.color.default_title_indicator_text_color); + final float defaultTextSize = res.getDimension(R.dimen.default_title_indicator_text_size); + final float defaultTitlePadding = res.getDimension(R.dimen.default_title_indicator_title_padding); + final float defaultClipPadding = res.getDimension(R.dimen.default_title_indicator_clip_padding); + final float defaultTopPadding = res.getDimension(R.dimen.default_title_indicator_top_padding); + + //Retrieve styles attributes + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TitlePageIndicator, defStyle, R.style.Widget_TitlePageIndicator); + + //Retrieve the colors to be used for this view and apply them. + mFooterLineHeight = a.getDimension(R.styleable.TitlePageIndicator_footerLineHeight, defaultFooterLineHeight); + mFooterIndicatorStyle = IndicatorStyle.fromValue(a.getInteger(R.styleable.TitlePageIndicator_footerIndicatorStyle, defaultFooterIndicatorStyle)); + mFooterIndicatorHeight = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorHeight, defaultFooterIndicatorHeight); + mFooterIndicatorUnderlinePadding = a.getDimension(R.styleable.TitlePageIndicator_footerIndicatorUnderlinePadding, defaultFooterIndicatorUnderlinePadding); + mFooterPadding = a.getDimension(R.styleable.TitlePageIndicator_footerPadding, defaultFooterPadding); + mTopPadding = a.getDimension(R.styleable.TitlePageIndicator_topPadding, defaultTopPadding); + mTitlePadding = a.getDimension(R.styleable.TitlePageIndicator_titlePadding, defaultTitlePadding); + mClipPadding = a.getDimension(R.styleable.TitlePageIndicator_clipPadding, defaultClipPadding); + mColorSelected = a.getColor(R.styleable.TitlePageIndicator_selectedColor, defaultSelectedColor); + mColorText = a.getColor(R.styleable.TitlePageIndicator_textColor, defaultTextColor); + mBoldText = a.getBoolean(R.styleable.TitlePageIndicator_selectedBold, defaultSelectedBold); + + final float textSize = a.getDimension(R.styleable.TitlePageIndicator_textSize, defaultTextSize); + final int footerColor = a.getColor(R.styleable.TitlePageIndicator_footerColor, defaultFooterColor); + mPaintText = new Paint(); + mPaintText.setTextSize(textSize); + mPaintText.setAntiAlias(true); + mPaintFooterLine = new Paint(); + mPaintFooterLine.setStyle(Paint.Style.FILL_AND_STROKE); + mPaintFooterLine.setStrokeWidth(mFooterLineHeight); + mPaintFooterLine.setColor(footerColor); + mPaintFooterIndicator = new Paint(); + mPaintFooterIndicator.setStyle(Paint.Style.FILL_AND_STROKE); + mPaintFooterIndicator.setColor(footerColor); + + a.recycle(); + } + + + public int getFooterColor() { + return mPaintFooterLine.getColor(); + } + + public void setFooterColor(int footerColor) { + mPaintFooterLine.setColor(footerColor); + mPaintFooterIndicator.setColor(footerColor); + invalidate(); + } + + public float getFooterLineHeight() { + return mFooterLineHeight; + } + + public void setFooterLineHeight(float footerLineHeight) { + mFooterLineHeight = footerLineHeight; + mPaintFooterLine.setStrokeWidth(mFooterLineHeight); + invalidate(); + } + + public float getFooterIndicatorHeight() { + return mFooterIndicatorHeight; + } + + public void setFooterIndicatorHeight(float footerTriangleHeight) { + mFooterIndicatorHeight = footerTriangleHeight; + invalidate(); + } + + public float getFooterIndicatorPadding() { + return mFooterPadding; + } + + public void setFooterIndicatorPadding(float footerIndicatorPadding) { + mFooterPadding = footerIndicatorPadding; + invalidate(); + } + + public IndicatorStyle getFooterIndicatorStyle() { + return mFooterIndicatorStyle; + } + + public void setFooterIndicatorStyle(IndicatorStyle indicatorStyle) { + mFooterIndicatorStyle = indicatorStyle; + invalidate(); + } + + public int getSelectedColor() { + return mColorSelected; + } + + public void setSelectedColor(int selectedColor) { + mColorSelected = selectedColor; + invalidate(); + } + + public boolean isSelectedBold() { + return mBoldText; + } + + public void setSelectedBold(boolean selectedBold) { + mBoldText = selectedBold; + invalidate(); + } + + public int getTextColor() { + return mColorText; + } + + public void setTextColor(int textColor) { + mPaintText.setColor(textColor); + mColorText = textColor; + invalidate(); + } + + public float getTextSize() { + return mPaintText.getTextSize(); + } + + public void setTextSize(float textSize) { + mPaintText.setTextSize(textSize); + invalidate(); + } + + public float getTitlePadding() { + return this.mTitlePadding; + } + + public void setTitlePadding(float titlePadding) { + mTitlePadding = titlePadding; + invalidate(); + } + + public float getTopPadding() { + return this.mTopPadding; + } + + public void setTopPadding(float topPadding) { + mTopPadding = topPadding; + invalidate(); + } + + public float getClipPadding() { + return this.mClipPadding; + } + + public void setClipPadding(float clipPadding) { + mClipPadding = clipPadding; + invalidate(); + } + + /* + * (non-Javadoc) + * + * @see android.view.View#onDraw(android.graphics.Canvas) + */ + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + //Calculate views bounds + ArrayList<RectF> bounds = calculateAllBounds(mPaintText); + + final int count = mViewPager.getAdapter().getCount(); + final int countMinusOne = count - 1; + final float halfWidth = getWidth() / 2f; + final int left = getLeft(); + final float leftClip = left + mClipPadding; + final int width = getWidth(); + final int height = getHeight(); + final int right = left + width; + final float rightClip = right - mClipPadding; + + int page = mCurrentPage; + float offsetPercent; + if (mCurrentOffset <= halfWidth) { + offsetPercent = 1.0f * mCurrentOffset / width; + } else { + page += 1; + offsetPercent = 1.0f * (width - mCurrentOffset) / width; + } + final boolean currentSelected = (offsetPercent <= SELECTION_FADE_PERCENTAGE); + final boolean currentBold = (offsetPercent <= BOLD_FADE_PERCENTAGE); + final float selectedPercent = (SELECTION_FADE_PERCENTAGE - offsetPercent) / SELECTION_FADE_PERCENTAGE; + + //Verify if the current view must be clipped to the screen + RectF curPageBound = bounds.get(mCurrentPage); + float curPageWidth = curPageBound.right - curPageBound.left; + if (curPageBound.left < leftClip) { + //Try to clip to the screen (left side) + clipViewOnTheLeft(curPageBound, curPageWidth, left); + } + if (curPageBound.right > rightClip) { + //Try to clip to the screen (right side) + clipViewOnTheRight(curPageBound, curPageWidth, right); + } + + //Left views starting from the current position + if (mCurrentPage > 0) { + for (int i = mCurrentPage - 1; i >= 0; i--) { + RectF bound = bounds.get(i); + //Is left side is outside the screen + if (bound.left < leftClip) { + float w = bound.right - bound.left; + //Try to clip to the screen (left side) + clipViewOnTheLeft(bound, w, left); + //Except if there's an intersection with the right view + RectF rightBound = bounds.get(i + 1); + //Intersection + if (bound.right + mTitlePadding > rightBound.left) { + bound.left = rightBound.left - w - mTitlePadding; + bound.right = bound.left + w; + } + } + } + } + //Right views starting from the current position + if (mCurrentPage < countMinusOne) { + for (int i = mCurrentPage + 1 ; i < count; i++) { + RectF bound = bounds.get(i); + //If right side is outside the screen + if (bound.right > rightClip) { + float w = bound.right - bound.left; + //Try to clip to the screen (right side) + clipViewOnTheRight(bound, w, right); + //Except if there's an intersection with the left view + RectF leftBound = bounds.get(i - 1); + //Intersection + if (bound.left - mTitlePadding < leftBound.right) { + bound.left = leftBound.right + mTitlePadding; + bound.right = bound.left + w; + } + } + } + } + + //Now draw views + for (int i = 0; i < count; i++) { + //Get the title + RectF bound = bounds.get(i); + //Only if one side is visible + if ((bound.left > left && bound.left < right) || (bound.right > left && bound.right < right)) { + final boolean currentPage = (i == page); + //Only set bold if we are within bounds + mPaintText.setFakeBoldText(currentPage && currentBold && mBoldText); + + //Draw text as unselected + mPaintText.setColor(mColorText); + canvas.drawText(mTitleProvider.getTitle(i), bound.left, bound.bottom + mTopPadding, mPaintText); + + //If we are within the selected bounds draw the selected text + if (currentPage && currentSelected) { + mPaintText.setColor(mColorSelected); + mPaintText.setAlpha((int)((mColorSelected >>> 24) * selectedPercent)); + canvas.drawText(mTitleProvider.getTitle(i), bound.left, bound.bottom + mTopPadding, mPaintText); + } + } + } + + //Draw the footer line + mPath = new Path(); + mPath.moveTo(0, height - mFooterLineHeight / 2f); + mPath.lineTo(width, height - mFooterLineHeight / 2f); + mPath.close(); + canvas.drawPath(mPath, mPaintFooterLine); + + switch (mFooterIndicatorStyle) { + case Triangle: + mPath = new Path(); + mPath.moveTo(halfWidth, height - mFooterLineHeight - mFooterIndicatorHeight); + mPath.lineTo(halfWidth + mFooterIndicatorHeight, height - mFooterLineHeight); + mPath.lineTo(halfWidth - mFooterIndicatorHeight, height - mFooterLineHeight); + mPath.close(); + canvas.drawPath(mPath, mPaintFooterIndicator); + break; + + case Underline: + if (!currentSelected) { + break; + } + + RectF underlineBounds = bounds.get(page); + mPath = new Path(); + mPath.moveTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); + mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight); + mPath.lineTo(underlineBounds.right + mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); + mPath.lineTo(underlineBounds.left - mFooterIndicatorUnderlinePadding, height - mFooterLineHeight - mFooterIndicatorHeight); + mPath.close(); + + mPaintFooterIndicator.setAlpha((int)(0xFF * selectedPercent)); + canvas.drawPath(mPath, mPaintFooterIndicator); + mPaintFooterIndicator.setAlpha(0xFF); + break; + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + final int count = mViewPager.getAdapter().getCount(); + final int width = getWidth(); + final float halfWidth = width / 2f; + final float sixthWidth = width / 6f; + + if ((mCurrentPage > 0) && (event.getX() < halfWidth - sixthWidth)) { + mViewPager.setCurrentItem(mCurrentPage - 1); + return true; + } else if ((mCurrentPage < count - 1) && (event.getX() > halfWidth + sixthWidth)) { + mViewPager.setCurrentItem(mCurrentPage + 1); + return true; + } + } + + return super.onTouchEvent(event); + } + + /** + * Set bounds for the right textView including clip padding. + * + * @param curViewBound + * current bounds. + * @param curViewWidth + * width of the view. + */ + private void clipViewOnTheRight(RectF curViewBound, float curViewWidth, int right) { + curViewBound.right = right - mClipPadding; + curViewBound.left = curViewBound.right - curViewWidth; + } + + /** + * Set bounds for the left textView including clip padding. + * + * @param curViewBound + * current bounds. + * @param curViewWidth + * width of the view. + */ + private void clipViewOnTheLeft(RectF curViewBound, float curViewWidth, int left) { + curViewBound.left = left + mClipPadding; + curViewBound.right = mClipPadding + curViewWidth; + } + + /** + * Calculate views bounds and scroll them according to the current index + * + * @param paint + * @param currentIndex + * @return + */ + private ArrayList<RectF> calculateAllBounds(Paint paint) { + ArrayList<RectF> list = new ArrayList<RectF>(); + //For each views (If no values then add a fake one) + final int count = mViewPager.getAdapter().getCount(); + final int width = getWidth(); + final int halfWidth = width / 2; + for (int i = 0; i < count; i++) { + RectF bounds = calcBounds(i, paint); + float w = (bounds.right - bounds.left); + float h = (bounds.bottom - bounds.top); + bounds.left = (halfWidth) - (w / 2) - mCurrentOffset + ((i - mCurrentPage) * width); + bounds.right = bounds.left + w; + bounds.top = 0; + bounds.bottom = h; + list.add(bounds); + } + + return list; + } + + /** + * Calculate the bounds for a view's title + * + * @param index + * @param paint + * @return + */ + private RectF calcBounds(int index, Paint paint) { + //Calculate the text bounds + RectF bounds = new RectF(); + bounds.right = paint.measureText(mTitleProvider.getTitle(index)); + bounds.bottom = paint.descent() - paint.ascent(); + return bounds; + } + + + public void setViewPager(ViewPager view) { + if (view.getAdapter() == null) { + throw new IllegalStateException("ViewPager does not have adapter instance."); + } + if (!(view.getAdapter() instanceof TitleProvider)) { + throw new IllegalStateException("ViewPager adapter must implement TitleProvider to be used with TitlePageIndicator."); + } + mViewPager = view; + mViewPager.setOnPageChangeListener(this); + mTitleProvider = (TitleProvider)mViewPager.getAdapter(); + invalidate(); + } + + + public void setViewPager(ViewPager view, int initialPosition) { + setViewPager(view); + setCurrentItem(initialPosition); + } + + + public void setCurrentItem(int item) { + if (mViewPager == null) { + throw new IllegalStateException("ViewPager has not been bound."); + } + mViewPager.setCurrentItem(item); + mCurrentPage = item; + invalidate(); + } + + public void onPageScrollStateChanged(int state) { + mScrollState = state; + + if (mListener != null) { + mListener.onPageScrollStateChanged(state); + } + } + + + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + mCurrentPage = position; + mCurrentOffset = positionOffsetPixels; + invalidate(); + + if (mListener != null) { + mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); + } + } + + + public void onPageSelected(int position) { + if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { + mCurrentPage = position; + invalidate(); + } + + if (mListener != null) { + mListener.onPageSelected(position); + } + } + + public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { + mListener = listener; + } + + /* + * (non-Javadoc) + * + * @see android.view.View#onMeasure(int, int) + */ + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); + } + + /** + * Determines the width of this view + * + * @param measureSpec + * A measureSpec packed into an int + * @return The width of the view, honoring constraints from measureSpec + */ + private int measureWidth(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode != MeasureSpec.EXACTLY) { + throw new IllegalStateException(getClass().getSimpleName() + " can only be used in EXACTLY mode."); + } + result = specSize; + return result; + } + + /** + * Determines the height of this view + * + * @param measureSpec + * A measureSpec packed into an int + * @return The height of the view, honoring constraints from measureSpec + */ + private int measureHeight(int measureSpec) { + float result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + //We were told how big to be + result = specSize; + } else { + //Calculate the text bounds + RectF bounds = new RectF(); + bounds.bottom = mPaintText.descent()-mPaintText.ascent(); + result = bounds.bottom - bounds.top + mFooterLineHeight + mFooterPadding + mTopPadding; + if (mFooterIndicatorStyle != IndicatorStyle.None) { + result += mFooterIndicatorHeight; + } + } + return (int)result; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState savedState = (SavedState)state; + super.onRestoreInstanceState(savedState.getSuperState()); + mCurrentPage = savedState.currentPage; + requestLayout(); + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState savedState = new SavedState(superState); + savedState.currentPage = mCurrentPage; + return savedState; + } + + static class SavedState extends BaseSavedState { + int currentPage; + + public SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + currentPage = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(currentPage); + } + + public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } +} diff --git a/vdrmanager/app/src/main/java/com/viewpagerindicator/TitleProvider.java b/vdrmanager/app/src/main/java/com/viewpagerindicator/TitleProvider.java new file mode 100644 index 0000000..2a04b65 --- /dev/null +++ b/vdrmanager/app/src/main/java/com/viewpagerindicator/TitleProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011 Patrik Akerfeldt + * + * 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.viewpagerindicator; + +/** + * A TitleProvider provides the title to display according to a view. + */ +public interface TitleProvider { + /** + * Returns the title of the view at position + * @param position + * @return + */ + public String getTitle(int position); +} diff --git a/vdrmanager/app/src/main/java/de/androvdr/widget/AnimatedTextView.java b/vdrmanager/app/src/main/java/de/androvdr/widget/AnimatedTextView.java new file mode 100644 index 0000000..b6c120b --- /dev/null +++ b/vdrmanager/app/src/main/java/de/androvdr/widget/AnimatedTextView.java @@ -0,0 +1,101 @@ +package de.androvdr.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.TextView; + +/** + * Created by lado on 04.05.15. + */ +public class AnimatedTextView extends TextView { + private static final int IS_ANIMATING_TAG_ID = "isAnimating".hashCode(); + + private Animation fadeInAnimation; + private Animation fadeOutAnimation; + + public AnimatedTextView(Context context) { + super(context); + + initAnimations(context); + } + + public AnimatedTextView(Context context, AttributeSet attrs) { + super(context, attrs); + + initAnimations(context); + } + + public AnimatedTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + initAnimations(context); + } + + public void initAnimations(Context context) { + fadeInAnimation = AnimationUtils.loadAnimation(this.getContext(), android.R.anim.fade_in); + + fadeOutAnimation = new AlphaAnimation(1.0f, 0.0f); + fadeOutAnimation.setDuration(100); + fadeInAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + setAnimatingFlag(true); + } + + @Override + public void onAnimationEnd(Animation animation) { + setAnimatingFlag(false); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + + fadeOutAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + setAnimatingFlag(false); + } + + @Override + public void onAnimationEnd(Animation animation) { + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + + }); + + setAnimatingFlag(false); + } + + public void fadeOut() { + if (getVisibility() == View.VISIBLE) { + startAnimation(fadeOutAnimation); + setVisibility(View.INVISIBLE); + } + } + + public void fadeIn() { + //if (getVisibility() == View.INVISIBLE && !isAnimating()) { +// startAnimation(fadeInAnimation); + setVisibility(View.VISIBLE); + // } + } + + private boolean isAnimating() { + return (Boolean) getTag(IS_ANIMATING_TAG_ID) == true; + } + + private void setAnimatingFlag(boolean isAnimating) { + setTag(IS_ANIMATING_TAG_ID, new Boolean(isAnimating)); + } +} diff --git a/vdrmanager/app/src/main/java/de/androvdr/widget/FontAwesome.java b/vdrmanager/app/src/main/java/de/androvdr/widget/FontAwesome.java new file mode 100644 index 0000000..77d7c07 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/androvdr/widget/FontAwesome.java @@ -0,0 +1,25 @@ +package de.androvdr.widget; + +import android.content.Context; +import android.graphics.Typeface; + +/** + * Created by lado on 04.05.15. + */ +public class FontAwesome { + + private static Typeface mFont; + + + public static Typeface getFontAwesome(Context context){ + + if(mFont != null){ + return mFont; + } + + mFont = Typeface.createFromAsset(context.getAssets(), "fonts/fontawesome-webfont.ttf"); + return mFont; + } + + +} diff --git a/vdrmanager/app/src/main/java/de/androvdr/widget/FontAwesomeButton.java b/vdrmanager/app/src/main/java/de/androvdr/widget/FontAwesomeButton.java new file mode 100644 index 0000000..22c4de1 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/androvdr/widget/FontAwesomeButton.java @@ -0,0 +1,36 @@ +package de.androvdr.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Button; + +/** + * Created by lado on 04.05.15. + */ +public class FontAwesomeButton extends Button { + + + public FontAwesomeButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + initFontAwesome(); + + } + + public FontAwesomeButton(Context context, AttributeSet attrs) { + super(context, attrs); + initFontAwesome(); + + } + + public FontAwesomeButton(Context context) { + super(context); + initFontAwesome(); + } + + private void initFontAwesome(){ + if(isInEditMode() == false) { + setTypeface(FontAwesome.getFontAwesome(getContext().getApplicationContext().getApplicationContext())); + } + } + +} diff --git a/vdrmanager/app/src/main/java/de/androvdr/widget/LruCache.java b/vdrmanager/app/src/main/java/de/androvdr/widget/LruCache.java new file mode 100644 index 0000000..9186b15 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/androvdr/widget/LruCache.java @@ -0,0 +1,396 @@ +package de.androvdr.widget; + +/** + * Created by lado on 04.05.15. + */ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + + + import java.util.LinkedHashMap; + import java.util.Map; + +/** + * BEGIN LAYOUTLIB CHANGE + * This is a custom version that doesn't use the non standard LinkedHashMap#eldest. + * END LAYOUTLIB CHANGE + * + * A cache that holds strong references to a limited number of values. Each time + * a value is accessed, it is moved to the head of a queue. When a value is + * added to a full cache, the value at the end of that queue is evicted and may + * become eligible for garbage collection. + * + * <p>If your cached values hold resources that need to be explicitly released, + * override {@link #entryRemoved}. + * + * <p>If a cache miss should be computed on demand for the corresponding keys, + * override {@link #create}. This simplifies the calling code, allowing it to + * assume a value will always be returned, even when there's a cache miss. + * + * <p>By default, the cache size is measured in the number of entries. Override + * {@link #sizeOf} to size the cache in different units. For example, this cache + * is limited to 4MiB of bitmaps: + * <pre> {@code + * int cacheSize = 4 * 1024 * 1024; // 4MiB + * LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) { + * protected int sizeOf(String key, Bitmap value) { + * return value.getByteCount(); + * } + * }}</pre> + * + * <p>This class is thread-safe. Perform multiple cache operations atomically by + * synchronizing on the cache: <pre> {@code + * synchronized (cache) { + * if (cache.get(key) == null) { + * cache.put(key, value); + * } + * }}</pre> + * + * <p>This class does not allow null to be used as a key or value. A return + * value of null from {@link #get}, {@link #put} or {@link #remove} is + * unambiguous: the key was not in the cache. + * + * <p>This class appeared in Android 3.1 (Honeycomb MR1); it's available as part + * of <a href="http://developer.android.com/sdk/compatibility-library.html">Android's + * Support Package</a> for earlier releases. + */ +public class LruCache<K, V> { + private final LinkedHashMap<K, V> map; + + /** Size of this cache in units. Not necessarily the number of elements. */ + private int size; + private int maxSize; + + private int putCount; + private int createCount; + private int evictionCount; + private int hitCount; + private int missCount; + + /** + * @param maxSize for caches that do not override {@link #sizeOf}, this is + * the maximum number of entries in the cache. For all other caches, + * this is the maximum sum of the sizes of the entries in this cache. + */ + public LruCache(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + this.maxSize = maxSize; + this.map = new LinkedHashMap<K, V>(0, 0.75f, true); + } + + /** + * Sets the size of the cache. + * @param maxSize The new maximum size. + * + * @hide + */ + public void resize(int maxSize) { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + + synchronized (this) { + this.maxSize = maxSize; + } + trimToSize(maxSize); + } + + /** + * Returns the value for {@code key} if it exists in the cache or can be + * created by {@code #create}. If a value was returned, it is moved to the + * head of the queue. This returns null if a value is not cached and cannot + * be created. + */ + public final V get(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V mapValue; + synchronized (this) { + mapValue = map.get(key); + if (mapValue != null) { + hitCount++; + return mapValue; + } + missCount++; + } + + /* + * Attempt to create a value. This may take a long time, and the map + * may be different when create() returns. If a conflicting value was + * added to the map while create() was working, we leave that value in + * the map and release the created value. + */ + + V createdValue = create(key); + if (createdValue == null) { + return null; + } + + synchronized (this) { + createCount++; + mapValue = map.put(key, createdValue); + + if (mapValue != null) { + // There was a conflict so undo that last put + map.put(key, mapValue); + } else { + size += safeSizeOf(key, createdValue); + } + } + + if (mapValue != null) { + entryRemoved(false, key, createdValue, mapValue); + return mapValue; + } else { + trimToSize(maxSize); + return createdValue; + } + } + + /** + * Caches {@code value} for {@code key}. The value is moved to the head of + * the queue. + * + * @return the previous value mapped by {@code key}. + */ + public final V put(K key, V value) { + if (key == null || value == null) { + throw new NullPointerException("key == null || value == null"); + } + + V previous; + synchronized (this) { + putCount++; + size += safeSizeOf(key, value); + previous = map.put(key, value); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, value); + } + + trimToSize(maxSize); + return previous; + } + + /** + * @param maxSize the maximum size of the cache before returning. May be -1 + * to evict even 0-sized elements. + */ + private void trimToSize(int maxSize) { + while (true) { + K key; + V value; + synchronized (this) { + if (size < 0 || (map.isEmpty() && size != 0)) { + throw new IllegalStateException(getClass().getName() + + ".sizeOf() is reporting inconsistent results!"); + } + + if (size <= maxSize) { + break; + } + + // BEGIN LAYOUTLIB CHANGE + // get the last item in the linked list. + // This is not efficient, the goal here is to minimize the changes + // compared to the platform version. + Map.Entry<K, V> toEvict = null; + for (Map.Entry<K, V> entry : map.entrySet()) { + toEvict = entry; + } + // END LAYOUTLIB CHANGE + + if (toEvict == null) { + break; + } + + key = toEvict.getKey(); + value = toEvict.getValue(); + map.remove(key); + size -= safeSizeOf(key, value); + evictionCount++; + } + + entryRemoved(true, key, value, null); + } + } + + /** + * Removes the entry for {@code key} if it exists. + * + * @return the previous value mapped by {@code key}. + */ + public final V remove(K key) { + if (key == null) { + throw new NullPointerException("key == null"); + } + + V previous; + synchronized (this) { + previous = map.remove(key); + if (previous != null) { + size -= safeSizeOf(key, previous); + } + } + + if (previous != null) { + entryRemoved(false, key, previous, null); + } + + return previous; + } + + /** + * Called for entries that have been evicted or removed. This method is + * invoked when a value is evicted to make space, removed by a call to + * {@link #remove}, or replaced by a call to {@link #put}. The default + * implementation does nothing. + * + * <p>The method is called without synchronization: other threads may + * access the cache while this method is executing. + * + * @param evicted true if the entry is being removed to make space, false + * if the removal was caused by a {@link #put} or {@link #remove}. + * @param newValue the new value for {@code key}, if it exists. If non-null, + * this removal was caused by a {@link #put}. Otherwise it was caused by + * an eviction or a {@link #remove}. + */ + protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} + + /** + * Called after a cache miss to compute a value for the corresponding key. + * Returns the computed value or null if no value can be computed. The + * default implementation returns null. + * + * <p>The method is called without synchronization: other threads may + * access the cache while this method is executing. + * + * <p>If a value for {@code key} exists in the cache when this method + * returns, the created value will be released with {@link #entryRemoved} + * and discarded. This can occur when multiple threads request the same key + * at the same time (causing multiple values to be created), or when one + * thread calls {@link #put} while another is creating a value for the same + * key. + */ + protected V create(K key) { + return null; + } + + private int safeSizeOf(K key, V value) { + int result = sizeOf(key, value); + if (result < 0) { + throw new IllegalStateException("Negative size: " + key + "=" + value); + } + return result; + } + + /** + * Returns the size of the entry for {@code key} and {@code value} in + * user-defined units. The default implementation returns 1 so that size + * is the number of entries and max size is the maximum number of entries. + * + * <p>An entry's size must not change while it is in the cache. + */ + protected int sizeOf(K key, V value) { + return 1; + } + + /** + * Clear the cache, calling {@link #entryRemoved} on each removed entry. + */ + public final void evictAll() { + trimToSize(-1); // -1 will evict 0-sized elements + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the number + * of entries in the cache. For all other caches, this returns the sum of + * the sizes of the entries in this cache. + */ + public synchronized final int size() { + return size; + } + + /** + * For caches that do not override {@link #sizeOf}, this returns the maximum + * number of entries in the cache. For all other caches, this returns the + * maximum sum of the sizes of the entries in this cache. + */ + public synchronized final int maxSize() { + return maxSize; + } + + /** + * Returns the number of times {@link #get} returned a value that was + * already present in the cache. + */ + public synchronized final int hitCount() { + return hitCount; + } + + /** + * Returns the number of times {@link #get} returned null or required a new + * value to be created. + */ + public synchronized final int missCount() { + return missCount; + } + + /** + * Returns the number of times {@link #create(Object)} returned a value. + */ + public synchronized final int createCount() { + return createCount; + } + + /** + * Returns the number of times {@link #put} was called. + */ + public synchronized final int putCount() { + return putCount; + } + + /** + * Returns the number of values that have been evicted. + */ + public synchronized final int evictionCount() { + return evictionCount; + } + + /** + * Returns a copy of the current contents of the cache, ordered from least + * recently accessed to most recently accessed. + */ + public synchronized final Map<K, V> snapshot() { + return new LinkedHashMap<K, V>(map); + } + + @Override public synchronized final String toString() { + int accesses = hitCount + missCount; + int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; + return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", + maxSize, hitCount, missCount, hitPercent); + } +} diff --git a/vdrmanager/app/src/main/java/de/androvdr/widget/SquareButton.java b/vdrmanager/app/src/main/java/de/androvdr/widget/SquareButton.java new file mode 100644 index 0000000..6c3ad29 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/androvdr/widget/SquareButton.java @@ -0,0 +1,35 @@ +package de.androvdr.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.Button; + +public class SquareButton extends FontAwesomeButton { + + public SquareButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public SquareButton(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SquareButton(Context context) { + super(context); + } + + + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + + //Get canvas width and height + int w = MeasureSpec.getSize(widthMeasureSpec); + int h = MeasureSpec.getSize(heightMeasureSpec); + + w = Math.min(w, h); + h = w; + + setMeasuredDimension(w, h); + } +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/QueuedWork.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/QueuedWork.java new file mode 100644 index 0000000..09bb15b --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/QueuedWork.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.bjusystems.vdrmanager; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Internal utility class to keep track of process-global work that's + * outstanding and hasn't been finished yet. + * + * This was created for writing SharedPreference edits out + * asynchronously so we'd have a mechanism to wait for the writes in + * Activity.onPause and similar places, but we may use this mechanism + * for other things in the future. + * + * + */ +public class QueuedWork { + + // The set of Runnables that will finish or wait on any async + // activities started by the application. + private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers = + new ConcurrentLinkedQueue<Runnable>(); + + private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class + + /** + * Returns a single-thread Executor shared by the entire process, + * creating it if necessary. + */ + public static ExecutorService singleThreadExecutor() { + synchronized (QueuedWork.class) { + if (sSingleThreadExecutor == null) { + // TODO: can we give this single thread a thread name? + sSingleThreadExecutor = Executors.newSingleThreadExecutor(); + } + return sSingleThreadExecutor; + } + } + + /** + * Add a runnable to finish (or wait for) a deferred operation + * started in this context earlier. Typically finished by e.g. + * an Activity#onPause. Used by SharedPreferences$Editor#startCommit(). + * + * Note that this doesn't actually start it running. This is just + * a scratch set for callers doing async work to keep updated with + * what's in-flight. In the common case, caller code + * (e.g. SharedPreferences) will pretty quickly call remove() + * after an add(). The only time these Runnables are run is from + * waitToFinish(), below. + */ + public static void add(Runnable finisher) { + sPendingWorkFinishers.add(finisher); + } + + public static void remove(Runnable finisher) { + sPendingWorkFinishers.remove(finisher); + } + + /** + * Finishes or waits for async operations to complete. + * (e.g. SharedPreferences$Editor#startCommit writes) + * + * Is called from the Activity base class's onPause(), after + * BroadcastReceiver's onReceive, after Service command handling, + * etc. (so async work is never lost) + */ + public static void waitToFinish() { + Runnable toFinish; + while ((toFinish = sPendingWorkFinishers.poll()) != null) { + toFinish.run(); + } + } + + /** + * Returns true if there is pending work to be done. Note that the + * result is out of data as soon as you receive it, so be careful how you + * use it. + */ + public static boolean hasPendingWork() { + return !sPendingWorkFinishers.isEmpty(); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/StringUtils.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/StringUtils.java new file mode 100644 index 0000000..97050dc --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/StringUtils.java @@ -0,0 +1,180 @@ + +package de.bjusystems.vdrmanager; + +import java.util.ArrayList; + + + +public class StringUtils { + + /** + * An empty immutable <code>String</code> array. + */ + public static final String[] EMPTY_STRING_ARRAY = new String[0]; + + + public static final String EMPTY_STRING = ""; + + + /** + * Performs the logic for the <code>split</code> and + * <code>splitPreserveAllTokens</code> methods that return a maximum array + * length. + * + * @param str the String to parse, may be <code>null</code> + * @param separatorChars the separate character + * @param max the maximum number of elements to include in the + * array. A zero or negative value implies no limit. + * @param preserveAllTokens if <code>true</code>, adjacent separators are + * treated as empty token separators; if <code>false</code>, adjacent + * separators are treated as one separator. + * @return an array of parsed Strings, <code>null</code> if null String input + */ + private static String[] splitWorker(String str, String separatorChars, int max, boolean preserveAllTokens) { + // Performance tuned for 2.0 (JDK1.4) + // Direct code is quicker than StringTokenizer. + // Also, StringTokenizer uses isSpace() not isWhitespace() + + if (str == null) { + return null; + } + int len = str.length(); + if (len == 0) { + return EMPTY_STRING_ARRAY; + } + ArrayList<String> list = new ArrayList<String>(); + int sizePlus1 = 1; + int i = 0, start = 0; + boolean match = false; + boolean lastMatch = false; + if (separatorChars == null) { + // Null separator means use whitespace + while (i < len) { + if (Character.isWhitespace(str.charAt(i))) { + if (match || preserveAllTokens) { + lastMatch = true; + if (sizePlus1++ == max) { + i = len; + lastMatch = false; + } + list.add(str.substring(start, i)); + match = false; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + } else if (separatorChars.length() == 1) { + // Optimise 1 character case + char sep = separatorChars.charAt(0); + while (i < len) { + if (str.charAt(i) == sep) { + if (match || preserveAllTokens) { + lastMatch = true; + if (sizePlus1++ == max) { + i = len; + lastMatch = false; + } + list.add(str.substring(start, i)); + match = false; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + } else { + // standard case + while (i < len) { + if (separatorChars.indexOf(str.charAt(i)) >= 0) { + if (match || preserveAllTokens) { + lastMatch = true; + if (sizePlus1++ == max) { + i = len; + lastMatch = false; + } + list.add(str.substring(start, i)); + match = false; + } + start = ++i; + continue; + } + lastMatch = false; + match = true; + i++; + } + } + if (match || (preserveAllTokens && lastMatch)) { + list.add(str.substring(start, i)); + } + return (String[]) list.toArray(new String[list.size()]); + } + + /** + * <p>Splits the provided text into an array, separators specified, + * preserving all tokens, including empty tokens created by adjacent + * separators. This is an alternative to using StringTokenizer.</p> + * + * <p>The separator is not included in the returned String array. + * Adjacent separators are treated as separators for empty tokens. + * For more control over the split use the StrTokenizer class.</p> + * + * <p>A <code>null</code> input String returns <code>null</code>. + * A <code>null</code> separatorChars splits on whitespace.</p> + * + * <pre> + * StringUtils.splitPreserveAllTokens(null, *) = null + * StringUtils.splitPreserveAllTokens("", *) = [] + * StringUtils.splitPreserveAllTokens("abc def", null) = ["abc", "def"] + * StringUtils.splitPreserveAllTokens("abc def", " ") = ["abc", "def"] + * StringUtils.splitPreserveAllTokens("abc def", " ") = ["abc", "", def"] + * StringUtils.splitPreserveAllTokens("ab:cd:ef", ":") = ["ab", "cd", "ef"] + * StringUtils.splitPreserveAllTokens("ab:cd:ef:", ":") = ["ab", "cd", "ef", ""] + * StringUtils.splitPreserveAllTokens("ab:cd:ef::", ":") = ["ab", "cd", "ef", "", ""] + * StringUtils.splitPreserveAllTokens("ab::cd:ef", ":") = ["ab", "", cd", "ef"] + * StringUtils.splitPreserveAllTokens(":cd:ef", ":") = ["", cd", "ef"] + * StringUtils.splitPreserveAllTokens("::cd:ef", ":") = ["", "", cd", "ef"] + * StringUtils.splitPreserveAllTokens(":cd:ef:", ":") = ["", cd", "ef", ""] + * </pre> + * + * @param str the String to parse, may be <code>null</code> + * @param separatorChars the characters used as the delimiters, + * <code>null</code> splits on whitespace + * @return an array of parsed Strings, <code>null</code> if null String input + * @since 2.1 + */ + public static String[] splitPreserveAllTokens(String str, String separatorChars) { + return splitWorker(str, separatorChars, -1, true); + } + + // Equals + //----------------------------------------------------------------------- + /** + * <p>Compares two Strings, returning <code>true</code> if they are equal.</p> + * + * <p><code>null</code>s are handled without exceptions. Two <code>null</code> + * references are considered to be equal. The comparison is case sensitive.</p> + * + * <pre> + * StringUtils.equals(null, null) = true + * StringUtils.equals(null, "abc") = false + * StringUtils.equals("abc", null) = false + * StringUtils.equals("abc", "abc") = true + * StringUtils.equals("abc", "ABC") = false + * </pre> + * + * @see java.lang.String#equals(Object) + * @param str1 the first String, may be null + * @param str2 the second String, may be null + * @return <code>true</code> if the Strings are equal, case sensitive, or + * both <code>null</code> + */ + public static boolean equals(String str1, String str2) { + return str1 == null ? str2 == null : str1.equals(str2); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/VdrSharedPreferencesImpl.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/VdrSharedPreferencesImpl.java new file mode 100644 index 0000000..97d6428 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/VdrSharedPreferencesImpl.java @@ -0,0 +1,527 @@ +/* + * Copyrigsht (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.bjusystems.vdrmanager; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.CountDownLatch; + +import android.content.SharedPreferences; +import android.os.Looper; + +import com.j256.ormlite.dao.RuntimeExceptionDao; + +import de.bjusystems.vdrmanager.data.Vdr; + +public class VdrSharedPreferencesImpl implements SharedPreferences { + + private static final String TAG = VdrSharedPreferencesImpl.class.getSimpleName(); + + private static final boolean DEBUG = false; + + // Lock ordering rules: + // - acquire SharedPreferencesImpl.this before EditorImpl.this + // - acquire mWritingToDiskLock before EditorImpl.this + + Vdr mVdr; + + + public Vdr getVdr(){ + return mVdr; + } + + RuntimeExceptionDao<Vdr, Integer> dao; + + private Map<String, Object> mMap; // guarded by 'this' + private int mDiskWritesInFlight = 0; // guarded by 'this' + private boolean mLoaded = false; // guarded by 'this' + + private final Object mWritingToDiskLock = new Object(); + private static final Object mContent = new Object(); + private final WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>(); + + public VdrSharedPreferencesImpl(Vdr vdr, RuntimeExceptionDao<Vdr, Integer> dao) { + mVdr = vdr; + this.dao = dao; + mLoaded = false; + mMap = null; + startLoadFromDisk(); + } + + private void startLoadFromDisk() { + synchronized (this) { + mLoaded = false; + } + new Thread("SharedPreferencesImpl-load") { + public void run() { + synchronized (VdrSharedPreferencesImpl.this) { + loadFromDiskLocked(); + } + } + }.start(); + } + + private void loadFromDiskLocked() { + if (mLoaded) { + return; + } + + Map map = mVdr.toMap(); + //StructStat stat = null; + //try { + //stat = Libcore.os.stat(mFile.getPath()); + //if (mFile.canRead()) { + //BufferedInputStream str = null; + //try { + //str = new BufferedInputStream(new FileInputStream(mFile), + // 16 * 1024); + //map = XmlUtils.readMapXml(str); + //} catch (XmlPullParserException e) { +// Log.w(TAG, "getSharedPreferences", e); + // } catch (FileNotFoundException e) { + // Log.w(TAG, "getSharedPreferences", e); + // } catch (IOException e) { + // Log.w(TAG, "getSharedPreferences", e); + //} finally { + //IoUtils.closeQuietly(str); + //} + //} + //} catch (ErrnoException e) { + //} + mLoaded = true; + if (map != null) { + mMap = map; + } else { + mMap = new HashMap<String, Object>(); + } + notifyAll(); + } + + + + public void registerOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + synchronized (this) { + mListeners.put(listener, mContent); + } + } + + public void unregisterOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + synchronized (this) { + mListeners.remove(listener); + } + } + + private void awaitLoadedLocked() { + // if (!mLoaded) { + // // Raise an explicit StrictMode onReadFromDisk for this + // // thread, since the real read will be in a different + // // thread and otherwise ignored by StrictMode. + // BlockGuard.getThreadPolicy().onReadFromDisk(); + // } + while (!mLoaded) { + try { + wait(); + } catch (InterruptedException unused) { + } + } + } + + public Map<String, ?> getAll() { + synchronized (this) { + awaitLoadedLocked(); + // noinspection unchecked + return new HashMap<String, Object>(mMap); + } + } + + public String getString(String key, String defValue) { + synchronized (this) { + awaitLoadedLocked(); + String v = String.valueOf(mMap.get(key)); + return v != null ? v : defValue; + } + } + + public Set<String> getStringSet(String key, Set<String> defValues) { + synchronized (this) { + awaitLoadedLocked(); + Set<String> v = (Set<String>) mMap.get(key); + return v != null ? v : defValues; + } + } + + public int getInt(String key, int defValue) { + synchronized (this) { + awaitLoadedLocked(); + Integer v = (Integer) mMap.get(key); + return v != null ? v : defValue; + } + } + + public long getLong(String key, long defValue) { + synchronized (this) { + awaitLoadedLocked(); + Long v = (Long) mMap.get(key); + return v != null ? v : defValue; + } + } + + public float getFloat(String key, float defValue) { + synchronized (this) { + awaitLoadedLocked(); + Float v = (Float) mMap.get(key); + return v != null ? v : defValue; + } + } + + public boolean getBoolean(String key, boolean defValue) { + synchronized (this) { + awaitLoadedLocked(); + Boolean v = (Boolean) mMap.get(key); + return v != null ? v : defValue; + } + } + + public boolean contains(String key) { + synchronized (this) { + awaitLoadedLocked(); + return mMap.containsKey(key); + } + } + + public Editor edit() { + // TODO: remove the need to call awaitLoadedLocked() when + // requesting an editor. will require some work on the + // Editor, but then we should be able to do: + // + // context.getSharedPreferences(..).edit().putString(..).apply() + // + // ... all without blocking. + synchronized (this) { + awaitLoadedLocked(); + } + + return new EditorImpl(); + } + + // Return value from EditorImpl#commitToMemory() + private static class MemoryCommitResult { + public boolean changesMade; // any keys different? + public List<String> keysModified; // may be null + public Set<OnSharedPreferenceChangeListener> listeners; // may be null + public Map<String, Object> mapToWriteToDisk; + public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); + public volatile boolean writeToDiskResult = false; + + public void setDiskWriteResult(boolean result) { + writeToDiskResult = result; + writtenToDiskLatch.countDown(); + } + } + + public final class EditorImpl implements Editor { + + private final Map<String, Object> mModified = new HashMap<String, Object>(); + + private boolean mClear = false; + + public Editor putString(String key, String value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + + public Editor putStringSet(String key, Set<String> values) { + synchronized (this) { + mModified.put(key, (values == null) ? null + : new HashSet<String>(values)); + return this; + } + } + + public Editor putInt(String key, int value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + + public Editor putLong(String key, long value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + + public Editor putFloat(String key, float value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + + public Editor putBoolean(String key, boolean value) { + synchronized (this) { + mModified.put(key, value); + return this; + } + } + + public Editor remove(String key) { + synchronized (this) { + mModified.put(key, this); + return this; + } + } + + public Editor clear() { + synchronized (this) { + mClear = true; + return this; + } + } + + public void apply() { + final MemoryCommitResult mcr = commitToMemory(); + final Runnable awaitCommit = new Runnable() { + public void run() { + try { + mcr.writtenToDiskLatch.await(); + } catch (InterruptedException ignored) { + } + } + }; + + QueuedWork.add(awaitCommit); + + Runnable postWriteRunnable = new Runnable() { + public void run() { + awaitCommit.run(); + QueuedWork.remove(awaitCommit); + } + }; + + VdrSharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); + + // Okay to notify the listeners before it's hit disk + // because the listeners should always get the same + // SharedPreferences instance back, which has the + // changes reflected in memory. + notifyListeners(mcr); + } + + // Returns true if any changes were made + private MemoryCommitResult commitToMemory() { + MemoryCommitResult mcr = new MemoryCommitResult(); + synchronized (VdrSharedPreferencesImpl.this) { + // We optimistically don't make a deep copy until + // a memory commit comes in when we're already + // writing to disk. + if (mDiskWritesInFlight > 0) { + // We can't modify our mMap as a currently + // in-flight write owns it. Clone it before + // modifying it. + // noinspection unchecked + mMap = new HashMap<String, Object>(mMap); + } + mcr.mapToWriteToDisk = mMap; + mDiskWritesInFlight++; + + boolean hasListeners = mListeners.size() > 0; + if (hasListeners) { + mcr.keysModified = new ArrayList<String>(); + mcr.listeners = new HashSet<OnSharedPreferenceChangeListener>( + mListeners.keySet()); + } + + synchronized (this) { + if (mClear) { + if (!mMap.isEmpty()) { + mcr.changesMade = true; + mMap.clear(); + } + mClear = false; + } + + for (Map.Entry<String, Object> e : mModified.entrySet()) { + String k = e.getKey(); + Object v = e.getValue(); + if (v == this) { // magic value for a removal mutation + if (!mMap.containsKey(k)) { + continue; + } + mMap.remove(k); + } else { + boolean isSame = false; + if (mMap.containsKey(k)) { + Object existingValue = mMap.get(k); + if (existingValue != null + && existingValue.equals(v)) { + continue; + } + } + mMap.put(k, v); + } + + mcr.changesMade = true; + if (hasListeners) { + mcr.keysModified.add(k); + } + } + + mModified.clear(); + } + } + return mcr; + } + + public boolean commit() { + MemoryCommitResult mcr = commitToMemory(); + VdrSharedPreferencesImpl.this.enqueueDiskWrite(mcr, null /* + * sync write on + * this thread + * okay + */); + try { + mcr.writtenToDiskLatch.await(); + } catch (InterruptedException e) { + return false; + } + notifyListeners(mcr); + return mcr.writeToDiskResult; + } + + private void notifyListeners(final MemoryCommitResult mcr) { + if (mcr.listeners == null || mcr.keysModified == null + || mcr.keysModified.size() == 0) { + return; + } + //if (Looper.myLooper() == Looper.getMainLooper()) { + for (int i = mcr.keysModified.size() - 1; i >= 0; i--) { + final String key = mcr.keysModified.get(i); + for (OnSharedPreferenceChangeListener listener : mcr.listeners) { + if (listener != null) { + listener.onSharedPreferenceChanged( + VdrSharedPreferencesImpl.this, key); + } + } + } + //} else { + // Run this function on the main thread. + // VdrManagerApp.sMainThreadHandler.post(new Runnable() { + // public void run() { + // notifyListeners(mcr); + // } + // }); + //} + } + } + + /** + * Enqueue an already-committed-to-memory result to be written to disk. + * + * They will be written to disk one-at-a-time in the order that they're + * enqueued. + * + * @param postWriteRunnable + * if non-null, we're being called from apply() and this is the + * runnable to run after the write proceeds. if null (from a + * regular commit()), then we're allowed to do this disk write on + * the main thread (which in addition to reducing allocations and + * creating a background thread, this has the advantage that we + * catch them in userdebug StrictMode reports to convert them + * where possible to apply() ...) + */ + private void enqueueDiskWrite(final MemoryCommitResult mcr, + final Runnable postWriteRunnable) { + final Runnable writeToDiskRunnable = new Runnable() { + public void run() { + synchronized (mWritingToDiskLock) { + writeToFile(mcr); + } + synchronized (VdrSharedPreferencesImpl.this) { + mDiskWritesInFlight--; + } + if (postWriteRunnable != null) { + postWriteRunnable.run(); + } + } + }; + + final boolean isFromSyncCommit = (postWriteRunnable == null); + + // Typical #commit() path with fewer allocations, doing a write on + // the current thread. + if (isFromSyncCommit) { + boolean wasEmpty = false; + synchronized (VdrSharedPreferencesImpl.this) { + wasEmpty = mDiskWritesInFlight == 1; + } + if (wasEmpty) { + writeToDiskRunnable.run(); + return; + } + } + + QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); + } + + // Note: must hold mWritingToDiskLock + private void writeToFile(MemoryCommitResult mcr) { + // Rename the current file so it may be used as a backup during the next + // read + + // Attempt to write the file, delete the backup and return true as + // atomically as + // possible. If any exception occurs, delete the new file; next time we + // will restore + // from the backup. + // FileOutputStream str = createFileOutputStream(mFile); + // if (str == null) { + // mcr.setDiskWriteResult(false); + // return; + // } + // + // XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); + mVdr.set(mcr.mapToWriteToDisk); + dao.createOrUpdate(mVdr); + // FileUtils.sync(str); + // str.close(); + // ContextImpl.setFilePermissionsFromMode(mFile.getPath(), mMode, 0); + // try { + // final StructStat stat = Libcore.os.stat(mFile.getPath()); + // synchronized (this) { + // mStatTimestamp = stat.st_mtime; + // mStatSize = stat.st_size; + // } + // } catch (ErrnoException e) { + // // Do nothing + // } + // Writing was successful, delete the backup file if there is one. + mcr.setDiskWriteResult(true); + return; + + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/ZonePicker.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/ZonePicker.java new file mode 100644 index 0000000..3b1dfbc --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/ZonePicker.java @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.bjusystems.vdrmanager; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; + +import org.xmlpull.v1.XmlPullParserException; + +import android.app.ListActivity; +import android.content.Context; +import android.content.Intent; +import android.content.res.XmlResourceParser; +import android.os.Bundle; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; +import android.widget.SimpleAdapter; + +/** + * The class displaying a list of time zones that match a filter string + * such as "Africa", "Europe", etc. Choosing an item from the list will set + * the time zone. Pressing Back without choosing from the list will not + * result in a change in the time zone setting. + */ +public class ZonePicker extends ListActivity { + private static final String TAG = "ZonePicker"; + + public static interface ZoneSelectionListener { + // You can add any argument if you really need it... + public void onZoneSelected(TimeZone tz); + } + + private static final String KEY_ID = "id"; // value: String + private static final String KEY_DISPLAYNAME = "name"; // value: String + private static final String KEY_GMT = "gmt"; // value: String + private static final String KEY_OFFSET = "offset"; // value: int (Integer) + private static final String XMLTAG_TIMEZONE = "timezone"; + + private static final int HOURS_1 = 60 * 60000; + + private static final int MENU_TIMEZONE = Menu.FIRST+1; + private static final int MENU_ALPHABETICAL = Menu.FIRST; + + private boolean mSortedByTimezone; + + private SimpleAdapter mTimezoneSortedAdapter; + private SimpleAdapter mAlphabeticalAdapter; + + private ZoneSelectionListener mListener; + + private String selectedTimeZone; + + /** + * Constructs an adapter with TimeZone list. Sorted by TimeZone in default. + * + * @param sortedByName use Name for sorting the list. + */ + public static SimpleAdapter constructTimezoneAdapter(Context context, + boolean sortedByName) { + return constructTimezoneAdapter(context, sortedByName, + android.R.layout.simple_list_item_2); + } + + /** + * Constructs an adapter with TimeZone list. Sorted by TimeZone in default. + * + * @param sortedByName use Name for sorting the list. + */ + public static SimpleAdapter constructTimezoneAdapter(Context context, + boolean sortedByName, int layoutId) { + final String[] from = new String[] {KEY_DISPLAYNAME, KEY_GMT}; + final int[] to = new int[] {android.R.id.text1, android.R.id.text2}; + + final String sortKey = (sortedByName ? KEY_DISPLAYNAME : KEY_OFFSET); + final MyComparator comparator = new MyComparator(sortKey); + final List<HashMap<String, Object>> sortedList = getZones(context); + Collections.sort(sortedList, comparator); + final SimpleAdapter adapter = new SimpleAdapter(context, + sortedList, + layoutId, + from, + to); + + return adapter; + } + + /** + * Searches {@link TimeZone} from the given {@link SimpleAdapter} object, and returns + * the index for the TimeZone. + * + * @param adapter SimpleAdapter constructed by + * {@link #constructTimezoneAdapter(Context, boolean)}. + * @param tz TimeZone to be searched. + * @return Index for the given TimeZone. -1 when there's no corresponding list item. + * returned. + */ + public static int getTimeZoneIndex(SimpleAdapter adapter, TimeZone tz) { + final String defaultId = tz.getID(); + final int listSize = adapter.getCount(); + for (int i = 0; i < listSize; i++) { + // Using HashMap<String, Object> induces unnecessary warning. + final HashMap<?,?> map = (HashMap<?,?>)adapter.getItem(i); + final String id = (String)map.get(KEY_ID); + if (defaultId.equals(id)) { + // If current timezone is in this list, move focus to it + return i; + } + } + return -1; + } + + /** + * @param item one of items in adapters. The adapter should be constructed by + * {@link #constructTimezoneAdapter(Context, boolean)}. + * @return TimeZone object corresponding to the item. + */ + public static TimeZone obtainTimeZoneFromItem(Object item) { + return TimeZone.getTimeZone((String)((Map<?, ?>)item).get(KEY_ID)); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + selectedTimeZone = getIntent().getStringExtra("current_tz"); + + mTimezoneSortedAdapter = constructTimezoneAdapter(this, false); + mAlphabeticalAdapter = constructTimezoneAdapter(this, true); + + // Sets the adapter + setSorting(true); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, MENU_ALPHABETICAL, 0, R.string.zone_list_menu_sort_alphabetically) + .setIcon(android.R.drawable.ic_menu_sort_alphabetically); + menu.add(0, MENU_TIMEZONE, 0, R.string.zone_list_menu_sort_by_timezone) + .setIcon(R.drawable.ic_menu_3d_globe); + return true; + } + + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + if (mSortedByTimezone) { + menu.findItem(MENU_TIMEZONE).setVisible(false); + menu.findItem(MENU_ALPHABETICAL).setVisible(true); + } else { + menu.findItem(MENU_TIMEZONE).setVisible(true); + menu.findItem(MENU_ALPHABETICAL).setVisible(false); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + + case MENU_TIMEZONE: + setSorting(true); + return true; + + case MENU_ALPHABETICAL: + setSorting(false); + return true; + + default: + return false; + } + } + + public void setZoneSelectionListener(ZoneSelectionListener listener) { + mListener = listener; + } + + private void setSorting(boolean sortByTimezone) { + final SimpleAdapter adapter = + sortByTimezone ? mTimezoneSortedAdapter : mAlphabeticalAdapter; + setListAdapter(adapter); + mSortedByTimezone = sortByTimezone; + final int defaultIndex = getTimeZoneIndex(adapter, TimeZone.getDefault()); + if (defaultIndex >= 0) { + setSelection(defaultIndex); + } + } + + private static List<HashMap<String, Object>> getZones(Context context) { + final List<HashMap<String, Object>> myData = new ArrayList<HashMap<String, Object>>(); + final long date = Calendar.getInstance().getTimeInMillis(); + try { + XmlResourceParser xrp = context.getResources().getXml(R.xml.timezones); + while (xrp.next() != XmlResourceParser.START_TAG) + continue; + xrp.next(); + while (xrp.getEventType() != XmlResourceParser.END_TAG) { + while (xrp.getEventType() != XmlResourceParser.START_TAG) { + if (xrp.getEventType() == XmlResourceParser.END_DOCUMENT) { + return myData; + } + xrp.next(); + } + if (xrp.getName().equals(XMLTAG_TIMEZONE)) { + String id = xrp.getAttributeValue(0); + String displayName = xrp.nextText(); + addItem(myData, id, displayName, date); + } + while (xrp.getEventType() != XmlResourceParser.END_TAG) { + xrp.next(); + } + xrp.next(); + } + xrp.close(); + } catch (XmlPullParserException xppe) { + Log.e(TAG, "Ill-formatted timezones.xml file"); + } catch (java.io.IOException ioe) { + Log.e(TAG, "Unable to read timezones.xml file"); + } + + return myData; + } + + private static void addItem( + List<HashMap<String, Object>> myData, String id, String displayName, long date) { + final HashMap<String, Object> map = new HashMap<String, Object>(); + map.put(KEY_ID, id); + map.put(KEY_DISPLAYNAME, displayName); + final TimeZone tz = TimeZone.getTimeZone(id); + final int offset = tz.getOffset(date); + final int p = Math.abs(offset); + final StringBuilder name = new StringBuilder(); + name.append("GMT"); + + if (offset < 0) { + name.append('-'); + } else { + name.append('+'); + } + + name.append(p / (HOURS_1)); + name.append(':'); + + int min = p / 60000; + min %= 60; + + if (min < 10) { + name.append('0'); + } + name.append(min); + + map.put(KEY_GMT, name.toString()); + map.put(KEY_OFFSET, offset); + + myData.add(map); + } + + @Override + public void onListItemClick(ListView listView, View v, int position, long id) { + final Map<?, ?> map = (Map<?, ?>)listView.getItemAtPosition(position); + final String tzId = (String) map.get(KEY_ID); + + // Update the system timezone value + //final AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + //alarm.setTimeZone(tzId); + final TimeZone tz = TimeZone.getTimeZone(tzId); + //if (mListener != null) { + // mListener.onZoneSelected(tz); + //} else { + // onBackPressed(); + ///} + Intent returnIntent = new Intent(); + if(tz == null){ + setResult(RESULT_CANCELED, returnIntent); + } else { + returnIntent.putExtra("new_tz",tz.getID()); + setResult(RESULT_OK, returnIntent); + } + finish(); + } + + private static class MyComparator implements Comparator<HashMap<?, ?>> { + private String mSortingKey; + + public MyComparator(String sortingKey) { + mSortingKey = sortingKey; + } + + public void setSortingKey(String sortingKey) { + mSortingKey = sortingKey; + } + + public int compare(HashMap<?, ?> map1, HashMap<?, ?> map2) { + Object value1 = map1.get(mSortingKey); + Object value2 = map2.get(mSortingKey); + + /* + * This should never happen, but just in-case, put non-comparable + * items at the end. + */ + if (!isComparable(value1)) { + return isComparable(value2) ? 1 : 0; + } else if (!isComparable(value2)) { + return -1; + } + + return ((Comparable) value1).compareTo(value2); + } + + private boolean isComparable(Object value) { + return (value != null) && (value instanceof Comparable); + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/app/C.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/app/C.java new file mode 100644 index 0000000..8156f69 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/app/C.java @@ -0,0 +1,15 @@ +package de.bjusystems.vdrmanager.app; + +/** + * + * Some constants + * @author lado + * + * + */ +public interface C { + + public static final String DATA_SEPARATOR = ":"; + + public static final long ONE_MINUTE_IN_MILLIS = 60 * 1000; +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/app/Intents.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/app/Intents.java new file mode 100644 index 0000000..72feb99 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/app/Intents.java @@ -0,0 +1,19 @@ +package de.bjusystems.vdrmanager.app; + +public interface Intents { + + //public static final String CURRENT_CHANNEL = "CURRENT_CHANNEL"; + public static final String HIGHLIGHT = "HIGHLIGHT"; + public static final String TIMER_OP = "TIMER_OP"; + public static final int EDIT_TIMER = 0; + public static final int ADD_TIMER = 1; + + public static final String VDR_ID = "VDR_ID"; + + public static final int EDIT_VDR = 2; + + public static final String EMPTY_CONFIG = "EMPTY_CONFIG"; + + public static final String CURRENT_EPG = "CURRENT_EPG"; + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/app/VdrManagerApp.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/app/VdrManagerApp.java new file mode 100644 index 0000000..fd301f0 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/app/VdrManagerApp.java @@ -0,0 +1,159 @@ +package de.bjusystems.vdrmanager.app; + +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import android.app.Activity; +import android.app.Application; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.EpgSearchParams; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.data.Vdr; + + +public class VdrManagerApp extends Application { + + + public VdrManagerApp() { + super(); + initSessionKeyStore(); + } + + public enum EpgListState { + EPG_TIME, EPG_CHANNEL, EPG_SEARCH + } + + private EpgListState epgListState; + private Event currentEvent; + private Timer currentTimer; + private Channel currentChannel; + + public static final Locale SYSTEM_LOCALE = Locale.getDefault(); + + private Vdr currentVDR; + + public Vdr getCurrentVDR() { + return currentVDR; + } + + public void setCurrentVDR(final Vdr currentVDR) { + this.currentVDR = currentVDR; + } + + private List<Event> currentEpgList = new ArrayList<Event>(); + + public List<Event> getCurrentEpgList() { + return currentEpgList; + } + + public void setCurrentEpgList(final List currentEpgList) { + this.currentEpgList = currentEpgList; + } + + private EpgSearchParams currentSearch; + private Class<? extends Activity> nextActivity; + private final List<Activity> activitiesToFinish = new ArrayList<Activity>(); + private boolean reload; + + @Override + public void onCreate() { + super.onCreate(); + Preferences.init(this); + } + + public void clear() { + this.currentEvent = null; + this.currentTimer = null; + this.currentChannel = null; + this.currentSearch = null; + this.currentEpgList = null; + this.epgListState = EpgListState.EPG_TIME; + } + + public Event getCurrentEvent() { + return currentEvent; + } + + public void setCurrentEvent(final Event currentEvent) { + this.currentEvent = currentEvent; + } + + public Timer getCurrentTimer() { + return currentTimer; + } + + public void setCurrentTimer(final Timer currentTimer) { + this.currentTimer = currentTimer; + } + + public Channel getCurrentChannel() { + return currentChannel; + } + + public void setCurrentChannel(final Channel currentChannel) { + clear(); + this.currentChannel = currentChannel; + this.epgListState = EpgListState.EPG_CHANNEL; + } + + public EpgSearchParams getCurrentSearch() { + return currentSearch; + } + + public void setCurrentSearch(final EpgSearchParams currentSearch) { + clear(); + this.currentSearch = currentSearch; + this.epgListState = EpgListState.EPG_SEARCH; + } + + public EpgListState getEpgListState() { + return epgListState; + } + + public Class<? extends Activity> getNextActivity() { + return nextActivity; + } + + public void setNextActivity(final Class<? extends Activity> nextActivity) { + this.nextActivity = nextActivity; + } + + public List<Activity> getActivitiesToFinish() { + return activitiesToFinish; + } + + public boolean isReload() { + return reload; + } + + public void setReload(final boolean reload) { + this.reload = reload; + } + + /** KeyStore for per app run accepted certificates */ + private KeyStore sessionKeyStore; + + /** + * Gets the temporary accepted certificates + * @return KeyStore + */ + public KeyStore getSessionKeyStore() { + return sessionKeyStore; + } + + /** + * Create a new and empty key store + */ + public void initSessionKeyStore() { + try { + sessionKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + sessionKeyStore.load(null); + } catch (final Exception e) { + sessionKeyStore = null; + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/AbstractSettingsActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/AbstractSettingsActivity.java new file mode 100644 index 0000000..ef6dd5e --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/AbstractSettingsActivity.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + +import android.content.Context; +import android.os.Bundle; +import android.preference.PreferenceActivity; +import android.preference.PreferenceManager; +import android.speech.tts.TextToSpeech; +import android.view.MenuItem; + +/** + * An abstract activity for all the settings activities. + * + * @author Jimmy Shih + */ +public class AbstractSettingsActivity extends PreferenceActivity { + + private BackupPreferencesListener backupPreferencesListener; + + @SuppressWarnings("deprecation") + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + setVolumeControlStream(TextToSpeech.Engine.DEFAULT_STREAM); + ApiAdapterFactory.getApiAdapter().configureActionBarHomeAsUp(this); + + PreferenceManager preferenceManager = getPreferenceManager(); + preferenceManager.setSharedPreferencesName(Constants.SETTINGS_NAME); + preferenceManager.setSharedPreferencesMode(Context.MODE_PRIVATE); + + // Set up automatic preferences backup + backupPreferencesListener = ApiAdapterFactory.getApiAdapter() + .getBackupPreferencesListener(this); + preferenceManager.getSharedPreferences() + .registerOnSharedPreferenceChangeListener(backupPreferencesListener); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() != android.R.id.home) { + return super.onOptionsItemSelected(item); + } + finish(); + return true; + } + + @SuppressWarnings("deprecation") + @Override + protected void onDestroy() { + super.onDestroy(); + PreferenceManager preferenceManager = getPreferenceManager(); + preferenceManager.getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(backupPreferencesListener); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api10Adapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api10Adapter.java new file mode 100644 index 0000000..0527f89 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api10Adapter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +import android.annotation.TargetApi; + + +/** + * API level 10 specific implementation of the {@link ApiAdapter}. + * + * @author Jimmy Shih + */ +@TargetApi(10) +public class Api10Adapter extends Api9Adapter { + + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api11Adapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api11Adapter.java new file mode 100644 index 0000000..ec26635 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api11Adapter.java @@ -0,0 +1,112 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + + +import java.util.List; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.SearchManager; +import android.content.Context; +import android.view.MenuItem; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.SearchView; + +/** + * API level 11 specific implementation of the {@link ApiAdapter}. + * + * @author Jimmy Shih + */ +@TargetApi(11) +public class Api11Adapter extends Api10Adapter { + + @Override + public void hideTitle(Activity activity) { + // Do nothing + } + + @Override + public void configureActionBarHomeAsUp(Activity activity) { + activity.getActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public void configureListViewContextualMenu(final Activity activity, ListView listView, + final ContextualActionModeCallback contextualActionModeCallback) { + /* + + listView.setOnItemLongClickListener(new OnItemLongClickListener() { + ActionMode actionMode; + @Override + public boolean onItemLongClick( + AdapterView<?> parent, View view, final int position, final long id) { + if (actionMode != null) { + return false; + } + actionMode = activity.startActionMode(new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mode.getMenuInflater().inflate(R.menu.list_context_menu, menu); + return true; + } + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // Return false to indicate no change. + return false; + } + @Override + public void onDestroyActionMode(ActionMode mode) { + actionMode = null; + } + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + mode.finish(); + return contextualActionModeCallback.onClick(item.getItemId(), position, id); + } + }); + TextView textView = (TextView) view.findViewById(R.id.list_item_name); + if (textView != null) { + actionMode.setTitle(textView.getText()); + } + view.setSelected(true); + return true; + } + }); + */ + }; + + @Override + public void configureSearchWidget(Activity activity, final MenuItem menuItem) { + SearchManager searchManager = (SearchManager) activity.getSystemService(Context.SEARCH_SERVICE); + SearchView searchView = (SearchView) menuItem.getActionView(); + searchView.setSearchableInfo(searchManager.getSearchableInfo(activity.getComponentName())); + searchView.setQueryRefinementEnabled(true); + } + + @Override + public boolean handleSearchMenuSelection(Activity activity) { + // Returns false to allow the platform to expand the search widget. + return false; + } + + @Override + public <T> void addAllToArrayAdapter(ArrayAdapter<T> arrayAdapter, List<T> items) { + arrayAdapter.addAll(items); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api14Adapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api14Adapter.java new file mode 100644 index 0000000..0425191 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api14Adapter.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + +import android.annotation.TargetApi; +import android.app.ActionBar; +import android.app.Activity; +import android.view.MenuItem; +import android.widget.SearchView; + +/** + * API level 14 specific implementation of the {@link ApiAdapter}. + * + * @author Jimmy Shih + */ +@TargetApi(14) +public class Api14Adapter extends Api11Adapter { + + @Override + public void configureActionBarHomeAsUp(Activity activity) { + ActionBar actionBar = activity.getActionBar(); + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + @Override + public void configureSearchWidget(Activity activity, final MenuItem menuItem) { + super.configureSearchWidget(activity, menuItem); + SearchView searchView = (SearchView) menuItem.getActionView(); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + menuItem.collapseActionView(); + return false; + } + @Override + public boolean onQueryTextChange(String newText) { + return false; + } + }); + searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() { + @Override + public boolean onSuggestionSelect(int position) { + return false; + } + @Override + public boolean onSuggestionClick(int position) { + menuItem.collapseActionView(); + return false; + } + }); + } + + @Override + public boolean handleSearchKey(MenuItem menuItem) { + menuItem.expandActionView(); + return true; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api7Adapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api7Adapter.java new file mode 100644 index 0000000..b89168a --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api7Adapter.java @@ -0,0 +1,107 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + + +import java.util.List; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.view.MenuItem; +import android.view.Window; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +/** + * API level 7 specific implementation of the {@link ApiAdapter}. + * + * @author Bartlomiej Niechwiej + */ +public class Api7Adapter implements ApiAdapter { + + + @Override + public BackupPreferencesListener getBackupPreferencesListener(Context context) { + return new BackupPreferencesListener() { + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + // Do nothing + } + }; + } + + @Override + public void applyPreferenceChanges(Editor editor) { + editor.commit(); + } + + @Override + public void enableStrictMode() { + // Not supported + } + + @Override + public byte[] copyByteArray(byte[] input, int start, int end) { + int length = end - start; + byte[] output = new byte[length]; + System.arraycopy(input, start, output, 0, length); + return output; + } + + + + @Override + public void hideTitle(Activity activity) { + activity.requestWindowFeature(Window.FEATURE_NO_TITLE); + } + + @Override + public void configureActionBarHomeAsUp(Activity activity) { + // Do nothing + } + + @Override + public void configureListViewContextualMenu(Activity activity, ListView listView, + ContextualActionModeCallback contextualActionModeCallback) { + activity.registerForContextMenu(listView); + } + + @Override + public void configureSearchWidget(Activity activity, MenuItem menuItem) { + // Do nothing + } + + @Override + public boolean handleSearchMenuSelection(Activity activity) { + activity.onSearchRequested(); + return true; + } + + @Override + public <T> void addAllToArrayAdapter(ArrayAdapter<T> arrayAdapter, List<T> items) { + for (T item : items) { + arrayAdapter.add(item); + } + } + + @Override + public boolean handleSearchKey(MenuItem menuItem) { + // Return false and allow the framework to handle the search key. + return false; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api8Adapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api8Adapter.java new file mode 100644 index 0000000..1fc61d3 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api8Adapter.java @@ -0,0 +1,32 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +import android.content.Context; + +/** + * API level 8 specific implementation of the {@link ApiAdapter}. + * + * @author Jimmy Shih + */ +public class Api8Adapter extends Api7Adapter { + + + @Override + public BackupPreferencesListener getBackupPreferencesListener(Context context) { + return new Api8BackupPreferencesListener(context); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api8BackupPreferencesListener.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api8BackupPreferencesListener.java new file mode 100644 index 0000000..90e3976 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api8BackupPreferencesListener.java @@ -0,0 +1,43 @@ +/* + * Copyright 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +import android.annotation.TargetApi; +import android.app.backup.BackupManager; +import android.content.Context; +import android.content.SharedPreferences; + +/** + * Implementation of {@link BackupPreferencesListener} that calls the + * {@link BackupManager}. <br> + * For API Level 8 or higher. + * + * @author Jimmy Shih + */ +@TargetApi(8) +public class Api8BackupPreferencesListener implements BackupPreferencesListener { + + private final BackupManager backupManager; + + public Api8BackupPreferencesListener(Context context) { + this.backupManager = new BackupManager(context); + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + backupManager.dataChanged(); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api9Adapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api9Adapter.java new file mode 100644 index 0000000..151f317 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Api9Adapter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +import java.util.Arrays; + +import android.annotation.TargetApi; +import android.content.SharedPreferences.Editor; +import android.os.StrictMode; +import android.util.Log; + +/** + * API level 9 specific implementation of the {@link ApiAdapter}. + * + * @author Rodrigo Damazio + */ +@TargetApi(9) +public class Api9Adapter extends Api8Adapter { + + @Override + public void applyPreferenceChanges(Editor editor) { + // Apply asynchronously + editor.apply(); + } + + @Override + public void enableStrictMode() { + Log.d(Constants.TAG, "Enabling strict mode"); + StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() + .detectDiskWrites() + .detectNetwork() + .penaltyLog() + .build()); + StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() + .detectAll() + .penaltyLog() + .build()); + } + + @Override + public byte[] copyByteArray(byte[] input, int start, int end) { + return Arrays.copyOfRange(input, start, end); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ApiAdapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ApiAdapter.java new file mode 100644 index 0000000..aa13297 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ApiAdapter.java @@ -0,0 +1,152 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +import java.util.List; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.view.MenuItem; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +/** + * A set of methods that may be implemented differently depending on the Android + * API level. + * + * @author Bartlomiej Niechwiej + */ +public interface ApiAdapter { + + + + /** + * Gets a {@link BackupPreferencesListener}. + * <p> + * Due to changes in API level 8. + * + * @param context the context + */ + public BackupPreferencesListener getBackupPreferencesListener(Context context); + + /** + * Applies all the changes done to a given preferences editor. Changes may or + * may not be applied immediately. + * <p> + * Due to changes in API level 9. + * + * @param editor the editor + */ + public void applyPreferenceChanges(SharedPreferences.Editor editor); + + /** + * Enables strict mode where supported, only if this is a development build. + * <p> + * Due to changes in API level 9. + */ + public void enableStrictMode(); + + /** + * Copies elements from an input byte array into a new byte array, from + * indexes start (inclusive) to end (exclusive). The end index must be less + * than or equal to the input length. + * <p> + * Due to changes in API level 9. + * + * @param input the input byte array + * @param start the start index + * @param end the end index + * @return a new array containing elements from the input byte array. + */ + public byte[] copyByteArray(byte[] input, int start, int end); + + + + + /** + * Hides the title. If the platform supports the action bar, do nothing. + * Ideally, with the action bar, we would like to collapse the navigation tabs + * into the action bar. However, collapsing is not supported by the + * compatibility library. + * <p> + * Due to changes in API level 11. + * + * @param activity the activity + */ + public void hideTitle(Activity activity); + + /** + * Configures the action bar with the Home button as an Up button. If the + * platform doesn't support the action bar, do nothing. + * <p> + * Due to changes in API level 11. + * + * @param activity the activity + */ + public void configureActionBarHomeAsUp(Activity activity); + + /** + * Configures the list view context menu. + * <p> + * Due to changes in API level 11. + * + * @param activity the activity + * @param listView the list view + * @param contextualActionModeCallback the callback when an item is selected + * in the contextual action mode + */ + public void configureListViewContextualMenu(Activity activity, ListView listView, + ContextualActionModeCallback contextualActionModeCallback); + + /** + * Configures the search widget. + * + * Due to changes in API level 11. + * + * @param activity the activity + * @param menuItem the search menu item + */ + public void configureSearchWidget(Activity activity, MenuItem menuItem); + + /** + * Handles the search menu selection. Returns true if handled. + * + * Due to changes in API level 11. + * + * @param activity the activity + */ + public boolean handleSearchMenuSelection(Activity activity); + + /** + * Adds all items to an array adapter. + * + * Due to changes in API level 11. + *s + * @param arrayAdapter the array adapter + * @param items list of items + */ + public <T> void addAllToArrayAdapter(ArrayAdapter<T> arrayAdapter, List<T> items); + + /** + * Handles the search key press. Returns true if handled. + * + * Due to changes in API level 14. + * + * @param menu the search menu + */ + public boolean handleSearchKey(MenuItem menu); +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ApiAdapterFactory.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ApiAdapterFactory.java new file mode 100644 index 0000000..5a14c10 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ApiAdapterFactory.java @@ -0,0 +1,56 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +import android.os.Build; + +/** + * A factory to get the {@link ApiAdapter} for the current device. + * + * @author Rodrigo Damazio + */ +public class ApiAdapterFactory { + + private static ApiAdapter apiAdapter; + + /** + * Gets the {@link ApiAdapter} for the current device. + */ + public static ApiAdapter getApiAdapter() { + if (apiAdapter == null) { + if (Build.VERSION.SDK_INT >= 14) { + apiAdapter = new Api14Adapter(); + return apiAdapter; + } else if (Build.VERSION.SDK_INT >= 11) { + apiAdapter = new Api11Adapter(); + return apiAdapter; + } else if (Build.VERSION.SDK_INT >= 10) { + apiAdapter = new Api10Adapter(); + return apiAdapter; + } else if (Build.VERSION.SDK_INT >= 9) { + apiAdapter = new Api9Adapter(); + return apiAdapter; + } else if (Build.VERSION.SDK_INT >= 8) { + apiAdapter = new Api8Adapter(); + return apiAdapter; + } else { + apiAdapter = new Api7Adapter(); + return apiAdapter; + } + } + return apiAdapter; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupActivity.java new file mode 100644 index 0000000..ed0da12 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupActivity.java @@ -0,0 +1,89 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.widget.Toast; +import de.bjusystems.vdrmanager.R; + +/** + * Activity to backup data to the SD card. + * + * @author Jimmy Shih + */ +public class BackupActivity extends Activity { + + private static final int DIALOG_PROGRESS_ID = 0; + + private BackupAsyncTask backupAsyncTask; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Object retained = getLastNonConfigurationInstance(); + if (retained instanceof BackupAsyncTask) { + backupAsyncTask = (BackupAsyncTask) retained; + backupAsyncTask.setActivity(this); + } else { + backupAsyncTask = new BackupAsyncTask(this); + backupAsyncTask.execute(); + } + } + + @Override + public Object onRetainNonConfigurationInstance() { + backupAsyncTask.setActivity(null); + return backupAsyncTask; + } + + @Override + protected Dialog onCreateDialog(int id) { + if (id != DIALOG_PROGRESS_ID) { + return null; + } + return DialogUtils.createSpinnerProgressDialog( + this, R.string.settings_backup_now_progress_message, new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }); + } + + /** + * Invokes when the associated AsyncTask completes. + * + * @param success true if the AsyncTask is successful + * @param messageId message id to display to user + */ + public void onAsyncTaskCompleted(boolean success, int messageId) { + removeDialog(DIALOG_PROGRESS_ID); + Toast.makeText(this, messageId, success ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show(); + finish(); + } + + /** + * Shows the progress dialog. + */ + public void showProgressDialog() { + showDialog(DIALOG_PROGRESS_ID); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupAsyncTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupAsyncTask.java new file mode 100644 index 0000000..8e98ac0 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupAsyncTask.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + + +import java.io.IOException; + +import android.os.AsyncTask; +import android.util.Log; +import de.bjusystems.vdrmanager.R; + +/** + * AsyncTask to backup data to the SD card. + * + * @author Jimmy Shih + */ +public class BackupAsyncTask extends AsyncTask<Void, Integer, Boolean> { + + private static final String TAG = BackupAsyncTask.class.getSimpleName(); + + private BackupActivity backupActivity; + private final ExternalFileBackup externalFileBackup; + + // true if the AsyncTask result is success + private boolean success; + + // true if the AsyncTask has completed + private boolean completed; + + // message id to return to the activity + private int messageId; + + /** + * Creates an AsyncTask. + * + * @param backupActivity the activity currently associated with this + * AsyncTask + */ + public BackupAsyncTask(BackupActivity backupActivity) { + this.backupActivity = backupActivity; + this.externalFileBackup = new ExternalFileBackup(backupActivity); + success = false; + completed = false; + messageId = R.string.sd_card_save_error; + } + + /** + * Sets the current {@link BackupActivity} associated with this AyncTask. + * + * @param activity the current {@link BackupActivity}, can be null + */ + public void setActivity(BackupActivity activity) { + this.backupActivity = activity; + if (completed && backupActivity != null) { + backupActivity.onAsyncTaskCompleted(success, messageId); + } + } + + @Override + protected void onPreExecute() { + if (backupActivity != null) { + backupActivity.showProgressDialog(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + if (!FileUtils.isSdCardAvailable()) { + messageId = R.string.sd_card_error_no_storage; + return false; + } + + if (!externalFileBackup.isBackupsDirectoryAvailable(true)) { + messageId = R.string.sd_card_save_error_create_dir; + return false; + } + + try { + externalFileBackup.writeToDefaultFile(); + messageId = R.string.sd_card_save_success; + return true; + } catch (IOException e) { + Log.d(TAG, "IO exception", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + success = result; + completed = true; + if (backupActivity != null) { + backupActivity.onAsyncTaskCompleted(success, messageId); + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupPreferencesListener.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupPreferencesListener.java new file mode 100644 index 0000000..c9a5b2f --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupPreferencesListener.java @@ -0,0 +1,27 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; + +/** + * Shared preferences listener which notifies the backup system about new data + * being available for backup. + * + * @author Rodrigo Damazio + */ +public interface BackupPreferencesListener extends OnSharedPreferenceChangeListener { +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupSettingsActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupSettingsActivity.java new file mode 100644 index 0000000..e906a20 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/BackupSettingsActivity.java @@ -0,0 +1,121 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + + +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceManager; +import de.bjusystems.vdrmanager.R; + +/** + * An activity for accessing the backup settings. + * + * @author Jimmy Shih + */ +public class BackupSettingsActivity extends AbstractSettingsActivity { + + private static final int DIALOG_CONFIRM_RESTORE_ID = 0; + + Preference backupPreference; + Preference restorePreference; + + /* + * Note that sharedPreferenceChangeListenr cannot be an anonymous inner class. + * Anonymous inner class will get garbage collected. + */ + private final OnSharedPreferenceChangeListener + sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { + @Override + public void onSharedPreferenceChanged(SharedPreferences preferences, String key) { + // Note that key can be null + //if (PreferencesUtils.getKey(BackupSettingsActivity.this, R.string.recording_track_id_key) + // .equals(key)) { + //updateUi(); + //} + } + }; + + @SuppressWarnings("deprecation") + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + PreferenceManager.getDefaultSharedPreferences(this) + .registerOnSharedPreferenceChangeListener(sharedPreferenceChangeListener); + + addPreferencesFromResource(R.xml.backup_settings); + backupPreference = findPreference(getString(R.string.settings_backup_now_key)); + backupPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = IntentUtils.newIntent(BackupSettingsActivity.this, BackupActivity.class); + startActivity(intent); + return true; + } + }); + restorePreference = findPreference(getString(R.string.settings_backup_restore_key)); + restorePreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + showDialog(DIALOG_CONFIRM_RESTORE_ID); + return true; + } + }); + } + + @Override + protected Dialog onCreateDialog(int id) { + if (id != DIALOG_CONFIRM_RESTORE_ID) { + return null; + } + return DialogUtils.createConfirmationDialog(this, + R.string.settings_backup_restore_confirm_message, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = IntentUtils.newIntent( + BackupSettingsActivity.this, RestoreChooserActivity.class); + startActivity(intent); + } + }); + } + + @Override + protected void onResume() { + super.onResume(); + //updateUi(); + } + +// /** +// * Updates the UI based on the recording state. +// */ +// private void updateUi() { +// boolean isRecording = PreferencesUtils.getLong(this, R.string.recording_track_id_key) +// != PreferencesUtils.RECORDING_TRACK_ID_DEFAULT; +// backupPreference.setEnabled(!isRecording); +// restorePreference.setEnabled(!isRecording); +// backupPreference.setSummary(isRecording ? R.string.settings_not_while_recording +// : R.string.settings_backup_now_summary); +// restorePreference.setSummary(isRecording ? R.string.settings_not_while_recording +// : R.string.settings_backup_restore_summary); +// } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Constants.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Constants.java new file mode 100644 index 0000000..491ec6f --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/Constants.java @@ -0,0 +1,135 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +/** + * Constants used by the MyTracks application. + * + * @author Leif Hendrik Wilden + */ +public abstract class Constants { + + /** + * Should be used by all log statements + */ + public static final String TAG = "vdrmanager"; + + /** + * Name of the top-level directory inside the SD card where our files will + * be read from/written to. + */ + public static final String SDCARD_TOP_DIR = "vdrmanager"; + + /** + * The number of distance readings to smooth to get a stable signal. + */ + public static final int DISTANCE_SMOOTHING_FACTOR = 25; + + /** + * The number of elevation readings to smooth to get a somewhat accurate + * signal. + */ + public static final int ELEVATION_SMOOTHING_FACTOR = 25; + + /** + * The number of grade readings to smooth to get a somewhat accurate signal. + */ + public static final int GRADE_SMOOTHING_FACTOR = 5; + + /** + * The number of speed reading to smooth to get a somewhat accurate signal. + */ + public static final int SPEED_SMOOTHING_FACTOR = 25; + + /** + * Maximum number of track points displayed by the map overlay. + */ + public static final int MAX_DISPLAYED_TRACK_POINTS = 10000; + + /** + * Target number of track points displayed by the map overlay. + * We may display more than this number of points. + */ + public static final int TARGET_DISPLAYED_TRACK_POINTS = 5000; + + /** + * Maximum number of track points ever loaded at once from the provider into + * memory. + * With a recording frequency of 2 seconds, 15000 corresponds to 8.3 hours. + */ + public static final int MAX_LOADED_TRACK_POINTS = 20000; + + /** + * Maximum number of track points ever loaded at once from the provider into + * memory in a single call to read points. + */ + public static final int MAX_LOADED_TRACK_POINTS_PER_BATCH = 1000; + + /** + * Maximum number of way points displayed by the map overlay. + */ + public static final int MAX_DISPLAYED_WAYPOINTS_POINTS = 128; + + /** + * Maximum number of way points that will be loaded at one time. + */ + public static final int MAX_LOADED_WAYPOINTS_POINTS = 10000; + + /** + * Any time segment where the distance traveled is less than this value will + * not be considered moving. + */ + public static final double MAX_NO_MOVEMENT_DISTANCE = 2; + + /** + * Anything faster than that (in meters per second) will be considered moving. + */ + public static final double MAX_NO_MOVEMENT_SPEED = 0.224; + + /** + * Ignore any acceleration faster than this. + * Will ignore any speeds that imply accelaration greater than 2g's + * 2g = 19.6 m/s^2 = 0.0002 m/ms^2 = 0.02 m/(m*ms) + */ + public static final double MAX_ACCELERATION = 0.02; + + /** Maximum age of a GPS location to be considered current. */ + public static final long MAX_LOCATION_AGE_MS = 60 * 1000; // 1 minute + + /** Maximum age of a network location to be considered current. */ + public static final long MAX_NETWORK_AGE_MS = 1000 * 60 * 10; // 10 minutes + + /** + * The type of account that we can use for gdata uploads. + */ + public static final String ACCOUNT_TYPE = "com.google"; + + /** + * The name of extra intent property to indicate whether we want to resume + * a previously recorded track. + */ + public static final String + RESUME_TRACK_EXTRA_NAME = "com.google.android.apps.mytracks.RESUME_TRACK"; + + public static final String MAPSHOP_BASE_URL = "https://maps.google.com/maps/ms"; + + public static final String SETTINGS_NAME = "SettingsActivity"; + + /** + * This is an abstract utility class. + */ + protected Constants() { } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ContentTypeIds.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ContentTypeIds.java new file mode 100644 index 0000000..bf48983 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ContentTypeIds.java @@ -0,0 +1,33 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +/** + * Utilities for serializing primitive types. + * + * @author Rodrigo Damazio + */ +public class ContentTypeIds { + public static final byte BOOLEAN_TYPE_ID = 0; + public static final byte LONG_TYPE_ID = 1; + public static final byte INT_TYPE_ID = 2; + public static final byte FLOAT_TYPE_ID = 3; + public static final byte DOUBLE_TYPE_ID = 4; + public static final byte STRING_TYPE_ID = 5; + public static final byte BLOB_TYPE_ID = 6; + + private ContentTypeIds() { /* Not instantiable */ } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ContextualActionModeCallback.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ContextualActionModeCallback.java new file mode 100644 index 0000000..d79d4be --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ContextualActionModeCallback.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + +/** + * Callback when an item in the contextual action mode is selected. + * + * @author Jimmy Shih + */ +public interface ContextualActionModeCallback { + + /** + * Invoked when an item is selected. + * + * @param itemId the context menu item id + * @param position the position of the selected row + * @param id the id of the selected row, if available + */ + public boolean onClick(int itemId, int position, long id); +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/DialogUtils.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/DialogUtils.java new file mode 100644 index 0000000..c9b716c --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/DialogUtils.java @@ -0,0 +1,101 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import de.bjusystems.vdrmanager.R; + +/** + * Utilities for creating dialogs. + * + * @author Jimmy Shih + */ +public class DialogUtils { + + private DialogUtils() {} + + /** + * Creates a confirmation dialog. + * + * @param context the context + * @param messageId the confirmation message id + * @param onClickListener the listener to invoke when the user clicks OK + */ + public static Dialog createConfirmationDialog( + Context context, int messageId, DialogInterface.OnClickListener onClickListener) { + return new AlertDialog.Builder(context) + .setCancelable(true) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(context.getString(messageId)) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, onClickListener) + .setTitle(R.string.generic_confirm_title) + .create(); + } + + /** + * Creates a spinner progress dialog. + * + * @param context the context + * @param messageId the progress message id + * @param onCancelListener the cancel listener + */ + public static ProgressDialog createSpinnerProgressDialog( + Context context, int messageId, DialogInterface.OnCancelListener onCancelListener) { + return createProgressDialog(true, context, messageId, onCancelListener); + } + + /** + * Creates a horizontal progress dialog. + * + * @param context the context + * @param messageId the progress message id + * @param onCancelListener the cancel listener + * @param formatArgs the format arguments for the messageId + */ + public static ProgressDialog createHorizontalProgressDialog(Context context, int messageId, + DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) { + return createProgressDialog(false, context, messageId, onCancelListener, formatArgs); + } + + /** + * Creates a progress dialog. + * + * @param spinner true to use the spinner style + * @param context the context + * @param messageId the progress message id + * @param onCancelListener the cancel listener + * @param formatArgs the format arguments for the message id + */ + private static ProgressDialog createProgressDialog(boolean spinner, Context context, + int messageId, DialogInterface.OnCancelListener onCancelListener, Object... formatArgs) { + ProgressDialog progressDialog = new ProgressDialog(context); + progressDialog.setCancelable(true); + progressDialog.setIcon(android.R.drawable.ic_dialog_info); + progressDialog.setIndeterminate(true); + progressDialog.setMessage(context.getString(messageId, formatArgs)); + progressDialog.setOnCancelListener(onCancelListener); + progressDialog.setProgressStyle(spinner ? ProgressDialog.STYLE_SPINNER + : ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setTitle(R.string.generic_progress_title); + return progressDialog; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ExternalFileBackup.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ExternalFileBackup.java new file mode 100644 index 0000000..790375e --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/ExternalFileBackup.java @@ -0,0 +1,277 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Log; +import de.bjusystems.vdrmanager.data.db.DBAccess; + + +/** + * Handler for writing or reading single-file backups. + * + * @author Rodrigo Damazio + */ +class ExternalFileBackup { + // Filename format - in UTC + private static final SimpleDateFormat BACKUP_FILENAME_FORMAT = new SimpleDateFormat( + "'backup-'yyyy-MM-dd_HH-mm-ss'.zip'"); + static { + BACKUP_FILENAME_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + private static final String BACKUPS_SUBDIR = "backups"; + private static final int BACKUP_FORMAT_VERSION = 1; + private static final String ZIP_ENTRY_NAME = "backup.mybillingbuddy.v" + + BACKUP_FORMAT_VERSION; + private static final int COMPRESSION_LEVEL = 8; + + private final Context context; + + public ExternalFileBackup(Context context) { + this.context = context; + } + + /** + * Returns whether the backups directory is (or can be made) available. + * + * @param create + * whether to try creating the directory if it doesn't exist + */ + public boolean isBackupsDirectoryAvailable(boolean create) { + return getBackupsDirectory(create) != null; + } + + /** + * Returns the backup directory, or null if not available. + * + * @param create + * whether to try creating the directory if it doesn't exist + */ + private File getBackupsDirectory(boolean create) { + String dirName = FileUtils.buildExternalDirectoryPath(BACKUPS_SUBDIR); + final File dir = new File(dirName); + Log.d(Constants.TAG, "Dir: " + dir.getAbsolutePath()); + if (create) { + // Try to create - if that fails, return null + return FileUtils.ensureDirectoryExists(dir) ? dir : null; + } else { + // Return it if it already exists, otherwise return null + return dir.isDirectory() ? dir : null; + } + } + + /** + * Returns a list of available backups to be restored. + */ + public Date[] getAvailableBackups() { + File dir = getBackupsDirectory(false); + if (dir == null) { + return null; + } + String[] fileNames = dir.list(); + + List<Date> backupDates = new ArrayList<Date>(fileNames.length); + for (int i = 0; i < fileNames.length; i++) { + String fileName = fileNames[i]; + try { + backupDates.add(BACKUP_FILENAME_FORMAT.parse(fileName)); + } catch (ParseException e) { + // Not a backup file, ignore + } + } + + return backupDates.toArray(new Date[backupDates.size()]); + } + + /** + * Writes the backup to the default file. + */ + public void writeToDefaultFile() throws IOException { + writeToFile(getFileForDate(new Date())); + } + + /** + * Restores the backup from the given date. + */ + public void restoreFromDate(Date when) throws IOException { + restoreFromFile(getFileForDate(when)); + } + + public void restoreFromFile(String path ) throws IOException { + restoreFromFile(new File(path)); + } + + /** + * Produces the proper file descriptor for the given backup date. + */ + private File getFileForDate(Date when) { + File dir = getBackupsDirectory(false); + String fileName = BACKUP_FILENAME_FORMAT.format(when); + File file = new File(dir, fileName); + return file; + } + + /** + * Synchronously writes a backup to the given file. + */ + private void writeToFile(File outputFile) throws IOException { + Log.d(Constants.TAG, + "Writing backup to file " + outputFile.getAbsolutePath()); + + // Create all the auxiliary classes that will do the writing + //DatabaseDumper trackDumper = new DatabaseDumper( + // BackupColumns.TRACKS_BACKUP_COLUMNS, + //BackupColumns.TRACKS_BACKUP_COLUMN_TYPES, false); + //DatabaseDumper waypointDumper = new DatabaseDumper( + // BackupColumns.WAYPOINTS_BACKUP_COLUMNS, + //BackupColumns.WAYPOINTS_BACKUP_COLUMN_TYPES, false); + //DatabaseDumper pointDumper = new DatabaseDumper( + // BackupColumns.POINTS_BACKUP_COLUMNS, + //BackupColumns.POINTS_BACKUP_COLUMN_TYPES, false); + + // Open the target for writing + FileOutputStream outputStream = new FileOutputStream(outputFile); + ZipOutputStream compressedStream = new ZipOutputStream(outputStream); + compressedStream.setLevel(COMPRESSION_LEVEL); + compressedStream.putNextEntry(new ZipEntry(ZIP_ENTRY_NAME)); + DataOutputStream outWriter = new DataOutputStream(compressedStream); + + try { + // Dump the entire contents of each table +// ContentResolver contentResolver = context.getContentResolver(); +// Cursor tracksCursor = contentResolver.query( +// TracksColumns.CONTENT_URI, null, null, null, null); +// try { +// trackDumper.writeAllRows(tracksCursor, outWriter); +// } finally { +// tracksCursor.close(); +// } +// +// Cursor waypointsCursor = contentResolver.query( +// WaypointsColumns.CONTENT_URI, null, null, null, null); +// try { +// waypointDumper.writeAllRows(waypointsCursor, outWriter); +// } finally { +// waypointsCursor.close(); +// } +// +// Cursor pointsCursor = contentResolver.query( +// TrackPointsColumns.CONTENT_URI, null, null, null, null); +// try { +// pointDumper.writeAllRows(pointsCursor, outWriter); +// } finally { +// pointsCursor.close(); +// } + + // Dump preferences + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + PreferenceBackupHelper preferencesHelper = new PreferenceBackupHelper(); + preferencesHelper.exportPreferences(preferences, outWriter); + + + File f = new File(DBAccess.getDataBaseFile()); + if(f.exists()){ + compressedStream.putNextEntry(new ZipEntry(DBAccess.DATABASE_NAME)); + IOUtils.copy(new FileInputStream(DBAccess.getDataBaseFile()), outWriter); + } + + + + + + + } catch (IOException e) { + // We tried to delete the partially created file, but do nothing + // if that also fails. + if (!outputFile.delete()) { + Log.w(Constants.TAG, + "Failed to delete file " + outputFile.getAbsolutePath()); + } + + throw e; + } finally { + compressedStream.closeEntry(); + compressedStream.close(); + } + } + + /** + * Synchronously restores the backup from the given file. + */ + private void restoreFromFile(File inputFile) throws IOException { + Log.d(Constants.TAG, + "Restoring from file " + inputFile.getAbsolutePath()); + + + + ZipFile zipFile = new ZipFile(inputFile, ZipFile.OPEN_READ); + ZipEntry zipEntry = zipFile.getEntry(ZIP_ENTRY_NAME); + if (zipEntry == null) { + throw new IOException("Invalid backup ZIP file"); + } + + InputStream compressedStream = zipFile.getInputStream(zipEntry); + DataInputStream reader = new DataInputStream(compressedStream); + + try { + + // Restore preferences + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); + PreferenceBackupHelper preferencesHelper = new PreferenceBackupHelper(); + preferencesHelper.importPreferences(reader, preferences); + + + + + zipEntry = zipFile.getEntry(DBAccess.DATABASE_NAME); + if (zipEntry != null) { + IOUtils.copy(zipFile.getInputStream(zipEntry), new FileOutputStream(DBAccess.getDataBaseFile())); + deleteJournal(DBAccess.getDataBaseFile()); + } + + } finally { + compressedStream.close(); + zipFile.close(); + } + } + + private static void deleteJournal(String db){ + if(db == null){ + return; + } + new File(db+"-journal").delete(); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/FileUtils.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/FileUtils.java new file mode 100644 index 0000000..d53a80b --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/FileUtils.java @@ -0,0 +1,199 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + + +import android.os.Environment; + +import java.io.File; + +/** + * Utilities for dealing with files. + * + * @author Rodrigo Damazio + */ +public class FileUtils { + + private FileUtils() {} + + /** + * The maximum FAT32 path length. See the FAT32 spec at + * http://msdn.microsoft.com/en-us/windows/hardware/gg463080 + */ + static final int MAX_FAT32_PATH_LENGTH = 260; + + /** + * Returns whether the SD card is available. + */ + public static boolean isSdCardAvailable() { + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + } + + /** + * Ensures the given directory exists by creating it and its parents if + * necessary. + * + * @return whether the directory exists (either already existed or was + * successfully created) + */ + public static boolean ensureDirectoryExists(File dir) { + if (dir.exists() && dir.isDirectory()) { + return true; + } + if (dir.mkdirs()) { + return true; + } + return false; + } + + /** + * Builds a path inside the My Tracks directory in the SD card. + * + * @param components the path components inside the mytracks directory + * @return the full path to the destination + */ + public static String buildExternalDirectoryPath(String... components) { + StringBuilder dirNameBuilder = new StringBuilder(); + dirNameBuilder.append(Environment.getExternalStorageDirectory()); + dirNameBuilder.append(File.separatorChar); + dirNameBuilder.append(Constants.SDCARD_TOP_DIR); + for (String component : components) { + dirNameBuilder.append(File.separatorChar); + dirNameBuilder.append(component); + } + return dirNameBuilder.toString(); + } + + /** + * Builds a filename with the given base name (prefix) and the given + * extension, possibly adding a suffix to ensure the file doesn't exist. + * + * @param directory the directory the file will live in + * @param fileBaseName the prefix for the file name + * @param extension the file's extension + * @return the complete file name, without the directory + */ + public static synchronized String buildUniqueFileName( + File directory, String fileBaseName, String extension) { + return buildUniqueFileName(directory, fileBaseName, extension, 0); + } + + /** + * Builds a filename with the given base and the given extension, possibly + * adding a suffix to ensure the file doesn't exist. + * + * @param directory the directory the filename will be located in + * @param base the base for the filename + * @param extension the extension for the filename + * @param suffix the first numeric suffix to try to use, or 0 for none + * @return the complete filename, without the directory + */ + private static String buildUniqueFileName( + File directory, String base, String extension, int suffix) { + String suffixName = ""; + if (suffix > 0) { + suffixName += "(" + Integer.toString(suffix) + ")"; + } + suffixName += "." + extension; + + String baseName = sanitizeFileName(base); + baseName = truncateFileName(directory, baseName, suffixName); + String fullName = baseName + suffixName; + + if (!new File(directory, fullName).exists()) { + return fullName; + } + return buildUniqueFileName(directory, base, extension, suffix + 1); + } + + /** + * Sanitizes the name as a valid fat32 filename. For simplicity, fat32 + * filename characters may be any combination of letters, digits, or + * characters with code point values greater than 127. Replaces the invalid + * characters with "_" and collapses multiple "_" together. + * + * @param name name + */ + static String sanitizeFileName(String name) { + StringBuffer buffer = new StringBuffer(name.length()); + for (int i = 0; i < name.length(); i++) { + int codePoint = name.codePointAt(i); + char character = name.charAt(i); + if (Character.isLetterOrDigit(character) || codePoint > 127 || isSpecialFat32(character)) { + buffer.appendCodePoint(codePoint); + } else { + buffer.append("_"); + } + } + String result = buffer.toString(); + return result.replaceAll("_+", "_"); + } + + /** + * Returns true if it is a special FAT32 character. + * + * @param character the character + */ + private static boolean isSpecialFat32(char character) { + switch (character) { + case '$': + case '%': + case '\'': + case '-': + case '_': + case '@': + case '~': + case '`': + case '!': + case '(': + case ')': + case '{': + case '}': + case '^': + case '#': + case '&': + case '+': + case ',': + case ';': + case '=': + case '[': + case ']': + case ' ': + return true; + default: + return false; + } + } + + /** + * Truncates the name if necessary so the filename path length (directory + + * name + suffix) meets the Fat32 path limit. + * + * @param directory directory + * @param name name + * @param suffix suffix + */ + static String truncateFileName(File directory, String name, String suffix) { + // 1 at the end accounts for the FAT32 filename trailing NUL character + int requiredLength = directory.getPath().length() + suffix.length() + 1; + if (name.length() + requiredLength > MAX_FAT32_PATH_LENGTH) { + int limit = MAX_FAT32_PATH_LENGTH - requiredLength; + return name.substring(0, limit); + } else { + return name; + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/IOUtils.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/IOUtils.java new file mode 100644 index 0000000..53be61c --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/IOUtils.java @@ -0,0 +1,217 @@ +package de.bjusystems.vdrmanager.backup; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You 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. + */ + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * General IO stream manipulation utilities. + * <p> + * This class provides static utility methods for input/output operations. + * <ul> + * <li>closeQuietly - these methods close a stream ignoring nulls and exceptions + * <li>toXxx/read - these methods read data from a stream + * <li>write - these methods write data to a stream + * <li>copy - these methods copy all the data from one stream to another + * <li>contentEquals - these methods compare the content of two streams + * </ul> + * <p> + * The byte-to-char methods and char-to-byte methods involve a conversion step. + * Two methods are provided in each case, one that uses the platform default + * encoding and the other which allows you to specify an encoding. You are + * encouraged to always specify an encoding because relying on the platform + * default can lead to unexpected results, for example when moving from + * development to production. + * <p> + * All the methods in this class that read a stream are buffered internally. + * This means that there is no cause to use a <code>BufferedInputStream</code> + * or <code>BufferedReader</code>. The default buffer size of 4K has been shown + * to be efficient in tests. + * <p> + * Wherever possible, the methods in this class do <em>not</em> flush or close + * the stream. This is to avoid making non-portable assumptions about the + * streams' origin and further use. Thus the caller is still responsible for + * closing streams after use. + * <p> + * Origin of code: Excalibur. + * + * @author Peter Donald + * @author Jeff Turner + * @author Matthew Hawthorne + * @author Stephen Colebourne + * @author Gareth Davis + * @author Ian Springer + * @author Niall Pemberton + * @author Sandy McArthur + * @version $Id: IOUtils.java 481854 2006-12-03 18:30:07Z scolebourne $ + */ +public class IOUtils { + // NOTE: This class is focussed on InputStream, OutputStream, Reader and + // Writer. Each method should take at least one of these as a parameter, + // or return one of them. + + /** + * The Unix directory separator character. + */ + public static final char DIR_SEPARATOR_UNIX = '/'; + /** + * The Windows directory separator character. + */ + public static final char DIR_SEPARATOR_WINDOWS = '\\'; + /** + * The system directory separator character. + */ + public static final char DIR_SEPARATOR = File.separatorChar; + /** + * The Unix line separator string. + */ + public static final String LINE_SEPARATOR_UNIX = "\n"; + /** + * The Windows line separator string. + */ + public static final String LINE_SEPARATOR_WINDOWS = "\r\n"; + /** + * The system line separator string. + */ + public static final String LINE_SEPARATOR; + static { + // avoid security issues + StringWriter buf = new StringWriter(4); + PrintWriter out = new PrintWriter(buf); + out.println(); + LINE_SEPARATOR = buf.toString(); + } + + /** + * The default buffer size to use. + */ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /** + * Instances should NOT be constructed in standard programming. + */ + public IOUtils() { + super(); + } + + // copy from InputStream + // ----------------------------------------------------------------------- + /** + * Copy bytes from an <code>InputStream</code> to an + * <code>OutputStream</code>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * <p> + * Large streams (over 2GB) will return a bytes copied value of + * <code>-1</code> after the copy has completed since the correct number of + * bytes cannot be returned as an int. For large streams use the + * <code>copyLarge(InputStream, OutputStream)</code> method. + * + * @param input + * the <code>InputStream</code> to read from + * @param output + * the <code>OutputStream</code> to write to + * @return the number of bytes copied + * @throws NullPointerException + * if the input or output is null + * @throws IOException + * if an I/O error occurs + * @throws ArithmeticException + * if the byte count is too large + * @since Commons IO 1.1 + */ + public static int copy(final InputStream input, final OutputStream output) + throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy bytes from a large (over 2GB) <code>InputStream</code> to an + * <code>OutputStream</code>. + * <p> + * This method buffers the input internally, so there is no need to use a + * <code>BufferedInputStream</code>. + * + * @param input + * the <code>InputStream</code> to read from + * @param output + * the <code>OutputStream</code> to write to + * @return the number of bytes copied + * @throws NullPointerException + * if the input or output is null + * @throws IOException + * if an I/O error occurs + * @since Commons IO 1.3 + */ + public static long copyLarge(final InputStream input, + final OutputStream output) throws IOException { + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + long count = 0; + int n = 0; + while (-1 != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + /** + * Unconditionally close an <code>InputStream</code>. + * <p> + * Equivalent to {@link InputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + * + * @param input the InputStream to close, may be null or already closed + */ + public static void closeQuietly(InputStream input) { + try { + if (input != null) { + input.close(); + } + } catch (IOException ioe) { + // ignore + } + } + + /** + * Unconditionally close an <code>OutputStream</code>. + * <p> + * Equivalent to {@link OutputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + * + * @param output the OutputStream to close, may be null or already closed + */ + public static void closeQuietly(OutputStream output) { + try { + if (output != null) { + output.close(); + } + } catch (IOException ioe) { + // ignore + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/IntentUtils.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/IntentUtils.java new file mode 100644 index 0000000..43131b4 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/IntentUtils.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + + +import android.content.Context; +import android.content.Intent; + +/** + * Utilities for creating intents. + * + * @author Jimmy Shih + */ +public class IntentUtils { + + public static final String TEXT_PLAIN_TYPE = "text/plain"; + + private IntentUtils() {} + + /** + * Creates an intent with {@link Intent#FLAG_ACTIVITY_CLEAR_TOP} and + * {@link Intent#FLAG_ACTIVITY_NEW_TASK}. + * + * @param context the context + * @param cls the class + */ + public static final Intent newIntent(Context context, Class<?> cls) { + return new Intent(context, cls).addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + } + + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/LineIterator.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/LineIterator.java new file mode 100644 index 0000000..c60ee66 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/LineIterator.java @@ -0,0 +1,5 @@ +package de.bjusystems.vdrmanager.backup; + +public class LineIterator { + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/PreferenceBackupHelper.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/PreferenceBackupHelper.java new file mode 100644 index 0000000..30a32b4 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/PreferenceBackupHelper.java @@ -0,0 +1,172 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package de.bjusystems.vdrmanager.backup; + + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Map; + +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; + +/** + * Helper for backing up and restoring shared preferences. + * + * @author Rodrigo Damazio + */ +class PreferenceBackupHelper { + + private static final int BUFFER_SIZE = 2048; + + /** + * Exports all shared preferences from the given object as a byte array. + * + * @param preferences the preferences to export + * @return the corresponding byte array + * @throws IOException if there are any errors while writing to the byte array + */ + public byte[] exportPreferences(SharedPreferences preferences) + throws IOException { + ByteArrayOutputStream bufStream = new ByteArrayOutputStream(BUFFER_SIZE); + DataOutputStream outWriter = new DataOutputStream(bufStream); + exportPreferences(preferences, outWriter); + + return bufStream.toByteArray(); + } + + /** + * Exports all shared preferences from the given object into the given output + * stream. + * + * @param preferences the preferences to export + * @param outWriter the stream to write them to + * @throws IOException if there are any errors while writing the output + */ + public void exportPreferences( + SharedPreferences preferences, + DataOutputStream outWriter) throws IOException { + Map<String, ?> values = preferences.getAll(); + + outWriter.writeInt(values.size()); + for (Map.Entry<String, ?> entry : values.entrySet()) { + writePreference(entry.getKey(), entry.getValue(), outWriter); + } + outWriter.flush(); + } + + /** + * Imports all preferences from the given byte array. + * + * @param data the byte array to read preferences from + * @param preferences the shared preferences to edit + * @throws IOException if there are any errors while reading + */ + public void importPreferences(byte[] data, SharedPreferences preferences) + throws IOException { + ByteArrayInputStream bufStream = new ByteArrayInputStream(data); + DataInputStream reader = new DataInputStream(bufStream); + + importPreferences(reader, preferences); + } + + /** + * Imports all preferences from the given stream. + * + * @param reader the stream to read from + * @param preferences the shared preferences to edit + * @throws IOException if there are any errors while reading + */ + public void importPreferences(DataInputStream reader, + SharedPreferences preferences) throws IOException { + Editor editor = preferences.edit(); + editor.clear(); + + int numPreferences = reader.readInt(); + for (int i = 0; i < numPreferences; i++) { + String name = reader.readUTF(); + byte typeId = reader.readByte(); + readAndSetPreference(name, typeId, reader, editor); + } + ApiAdapterFactory.getApiAdapter().applyPreferenceChanges(editor); + } + + /** + * Reads a single preference and sets it into the given editor. + * + * @param name the name of the preference to read + * @param typeId the type ID of the preference to read + * @param reader the reader to read from + * @param editor the editor to set the preference in + * @throws IOException if there are errors while reading + */ + private void readAndSetPreference(String name, byte typeId, + DataInputStream reader, Editor editor) throws IOException { + switch (typeId) { + case ContentTypeIds.BOOLEAN_TYPE_ID: + editor.putBoolean(name, reader.readBoolean()); + return; + case ContentTypeIds.LONG_TYPE_ID: + editor.putLong(name, reader.readLong()); + return; + case ContentTypeIds.FLOAT_TYPE_ID: + editor.putFloat(name, reader.readFloat()); + return; + case ContentTypeIds.INT_TYPE_ID: + editor.putInt(name, reader.readInt()); + return; + case ContentTypeIds.STRING_TYPE_ID: + editor.putString(name, reader.readUTF()); + return; + } + } + + /** + * Writes a single preference. + * + * @param name the name of the preference to write + * @param value the correctly-typed value of the preference + * @param writer the writer to write to + * @throws IOException if there are errors while writing + */ + private void writePreference(String name, Object value, DataOutputStream writer) + throws IOException { + writer.writeUTF(name); + + if (value instanceof Boolean) { + writer.writeByte(ContentTypeIds.BOOLEAN_TYPE_ID); + writer.writeBoolean((Boolean) value); + } else if (value instanceof Integer) { + writer.writeByte(ContentTypeIds.INT_TYPE_ID); + writer.writeInt((Integer) value); + } else if (value instanceof Long) { + writer.writeByte(ContentTypeIds.LONG_TYPE_ID); + writer.writeLong((Long) value); + } else if (value instanceof Float) { + writer.writeByte(ContentTypeIds.FLOAT_TYPE_ID); + writer.writeFloat((Float) value); + } else if (value instanceof String) { + writer.writeByte(ContentTypeIds.STRING_TYPE_ID); + writer.writeUTF((String) value); + } else { + throw new IllegalArgumentException( + "Type " + value.getClass().getName() + " not supported"); + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/RestoreActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/RestoreActivity.java new file mode 100644 index 0000000..97e4749 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/RestoreActivity.java @@ -0,0 +1,114 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + + + +import java.util.Date; + +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.widget.Toast; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.gui.PreferencesActivity; + +/** + * An activity to restore data from the SD card. + * + * @author Jimmy Shih + */ +public class RestoreActivity extends Activity { + + public static final String EXTRA_DATE = "date"; + public static final String EXTRA_FILE = "file"; + + private static final String TAG = RestoreActivity.class.getSimpleName(); + private static final int DIALOG_PROGRESS_ID = 0; + + private RestoreAsyncTask restoreAsyncTask; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Object retained = getLastNonConfigurationInstance(); + if (retained instanceof RestoreAsyncTask) { + restoreAsyncTask = (RestoreAsyncTask) retained; + restoreAsyncTask.setActivity(this); + } else { + + long date = -1L; + String file = getIntent().getStringExtra(EXTRA_FILE); + if(file != null){ + restoreAsyncTask = new RestoreAsyncTask(this, file); + } else if( (date = getIntent().getLongExtra(EXTRA_DATE, -1L)) != -1L) { + restoreAsyncTask = new RestoreAsyncTask(this, new Date(date)); + } else { + Log.d(TAG, "Invalid date or file"); + finish(); + return; + } + restoreAsyncTask.execute(); + } + } + + @Override + public Object onRetainNonConfigurationInstance() { + restoreAsyncTask.setActivity(null); + return restoreAsyncTask; + } + + @Override + protected Dialog onCreateDialog(int id) { + if (id != DIALOG_PROGRESS_ID) { + return null; + } + return DialogUtils.createSpinnerProgressDialog(this, + R.string.settings_backup_restore_progress_message, new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }); + } + + /** + * Invokes when the associated AsyncTask completes. + * + * @param success true if the AsyncTask is successful + * @param messageId message id to display to user + */ + public void onAsyncTaskCompleted(boolean success, int messageId) { + removeDialog(DIALOG_PROGRESS_ID); + Toast.makeText(this, messageId, success ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show(); + //Intent intent = IntentUtils.newIntent(this, FixUpJobs.class); + //intent.putExtra(Extras.AFTER_RESTORE, Boolean.TRUE); + Intent intent = IntentUtils.newIntent(this, PreferencesActivity.class); + startActivity(intent); + } + + /** + * Shows the progress dialog. + */ + public void showProgressDialog() { + showDialog(DIALOG_PROGRESS_ID); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/RestoreAsyncTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/RestoreAsyncTask.java new file mode 100644 index 0000000..37373f3 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/RestoreAsyncTask.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + +import java.io.IOException; +import java.util.Date; + +import android.os.AsyncTask; +import android.util.Log; +import de.bjusystems.vdrmanager.R; + +/** + * AsyncTask to restore data from the SD card. + * + * @author Jimmy Shih + */ +public class RestoreAsyncTask extends AsyncTask<Void, Integer, Boolean> { + + private static final String TAG = RestoreAsyncTask.class.getSimpleName(); + + private RestoreActivity restoreActivity; + private final Date date; + private final String path; + private final ExternalFileBackup externalFileBackup; + + // true if the AsyncTask result is success + private boolean success; + + // true if the AsyncTask has completed + private boolean completed; + + // message id to return to the activity + private int messageId; + + /** + * Creates an AsyncTask. + * + * @param restoreActivity + * the activity currently associated with this AsyncTask + * @param date + * the date to retore from + */ + public RestoreAsyncTask(RestoreActivity restoreActivity, Date date) { + this(restoreActivity, date, null); + } + + /** + * Creates an AsyncTask. + * + * @param restoreActivity + * the activity currently associated with this AsyncTask + * @param date + * the date to retore from + */ + public RestoreAsyncTask(RestoreActivity restoreActivity, String path) { + this(restoreActivity, null, path); + } + + /** + * Creates an AsyncTask. + * + * @param restoreActivity + * the activity currently associated with this AsyncTask + * @param date + * the date to retore from + */ + public RestoreAsyncTask(RestoreActivity restoreActivity, Date date, + String path) { + this.restoreActivity = restoreActivity; + this.date = date; + this.path = path; + this.externalFileBackup = new ExternalFileBackup(restoreActivity); + success = false; + completed = false; + messageId = R.string.sd_card_import_error; + } + + /** + * Sets the current {@link RestoreActivity} associated with this AyncTask. + * + * @param activity + * the current {@link RestoreActivity}, can be null + */ + public void setActivity(RestoreActivity activity) { + this.restoreActivity = activity; + if (completed && restoreActivity != null) { + restoreActivity.onAsyncTaskCompleted(success, messageId); + } + } + + @Override + protected void onPreExecute() { + if (restoreActivity != null) { + restoreActivity.showProgressDialog(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + if (path != null) { + externalFileBackup.restoreFromFile(path); + } else { + externalFileBackup.restoreFromDate(date); + } + messageId = R.string.sd_card_import_success; + return true; + } catch (IOException e) { + Log.d(TAG, "IO exception", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean result) { + success = result; + completed = true; + if (restoreActivity != null) { + restoreActivity.onAsyncTaskCompleted(success, messageId); + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/RestoreChooserActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/RestoreChooserActivity.java new file mode 100644 index 0000000..cba6ab0 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/backup/RestoreChooserActivity.java @@ -0,0 +1,127 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package de.bjusystems.vdrmanager.backup; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.widget.Toast; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.gui.Utils; + +/** + * An activity to choose a date to restore from. + * + * @author Jimmy Shih + */ +public class RestoreChooserActivity extends Activity { + + private static final Comparator<Date> REVERSE_DATE_ORDER = new Comparator<Date>() { + @Override + public int compare(Date s1, Date s2) { + return s2.compareTo(s1); + } + }; + private static final int DIALOG_CHOOSER_ID = 0; + + private Date[] backupDates; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + ExternalFileBackup externalFileBackup = new ExternalFileBackup(this); + + // Get the list of existing backups + if (!FileUtils.isSdCardAvailable()) { + Toast.makeText(this, R.string.sd_card_error_no_storage, + Toast.LENGTH_LONG).show(); + finish(); + return; + } + + if (!externalFileBackup.isBackupsDirectoryAvailable(false)) { + + Toast.makeText(this, R.string.settings_backup_restore_no_backup, + Toast.LENGTH_LONG).show(); + finish(); + return; + } + doStuff(externalFileBackup); + + } + + private void doStuff(ExternalFileBackup externalFileBackup) { + backupDates = externalFileBackup.getAvailableBackups(); + if (backupDates == null || backupDates.length == 0) { + Toast.makeText(this, R.string.settings_backup_restore_no_backup, + Toast.LENGTH_LONG).show(); + finish(); + return; + } + + if (backupDates.length == 1) { + Intent intent = IntentUtils.newIntent(this, RestoreActivity.class) + .putExtra(RestoreActivity.EXTRA_DATE, + backupDates[0].getTime()); + startActivity(intent); + finish(); + return; + } + + Arrays.sort(backupDates, REVERSE_DATE_ORDER); + showDialog(DIALOG_CHOOSER_ID); + } + + @Override + protected Dialog onCreateDialog(int id) { + if (id != DIALOG_CHOOSER_ID) { + return null; + } + String items[] = new String[backupDates.length]; + for (int i = 0; i < backupDates.length; i++) { + items[i] = Utils.formatDateTime(this, backupDates[i].getTime()); + } + return new AlertDialog.Builder(this) + .setCancelable(true) + .setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = IntentUtils.newIntent( + RestoreChooserActivity.this, + RestoreActivity.class).putExtra( + RestoreActivity.EXTRA_DATE, + backupDates[which].getTime()); + startActivity(intent); + finish(); + } + }).setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialog) { + finish(); + } + }).setTitle(R.string.settings_backup_restore_select_title) + .create(); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/AliveState.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/AliveState.java new file mode 100644 index 0000000..cf96efa --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/AliveState.java @@ -0,0 +1,36 @@ +package de.bjusystems.vdrmanager.data; + +public class AliveState { + + public static AliveState ALIVE = new AliveState(0); + public static AliveState DEAD = new AliveState(1); + public static AliveState UNKNOWN = new AliveState(2); + + private final int value; + private static AliveState state; + + private AliveState(final int value) { + this.value = value; + } + + public static AliveState getState() { + return state; + } + + public static void setState(final AliveState state) { + AliveState.state = state; + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof AliveState)) { + return false; + } + return this.value == ((AliveState)o).value; + } + + @Override + public int hashCode() { + return Integer.valueOf(value).hashCode(); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/AudioTrack.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/AudioTrack.java new file mode 100644 index 0000000..323507f --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/AudioTrack.java @@ -0,0 +1,61 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.ArrayList; +import java.util.List; + +public class AudioTrack { + + private String cached = null; + + public int index; + + public String type; + + public String display; + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(type).append(", ").append(display).append("+").append(index); + return sb.toString(); + }; + + private static final ArrayList<AudioTrack> EMPTY = new ArrayList<AudioTrack>( + 0); + + /** + * a,1,deu|d,2,deu + * + * @param raw + * @return + */ + public static List<AudioTrack> getAudio(String rawAudio) { + + if(rawAudio == null){ + return EMPTY; + } + + String[] splitted = rawAudio.split("\\|"); + + if (splitted == null || splitted.length == 0) { + return EMPTY; + } + + ArrayList<AudioTrack> audio; + audio = new ArrayList<AudioTrack>(splitted.length); + for (String a : splitted) { + String[] ar = a.split(","); + if (ar == null || ar.length != 3) { + continue; + } + AudioTrack track = new AudioTrack(); + track.type = ar[0]; + track.index = Integer.valueOf(ar[1]); + track.display = ar[2]; + audio.add(track); + } + return audio; + + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Channel.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Channel.java new file mode 100644 index 0000000..8aadb16 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Channel.java @@ -0,0 +1,176 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.List; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; + +import de.bjusystems.vdrmanager.StringUtils; +import de.bjusystems.vdrmanager.app.C; +import static de.bjusystems.vdrmanager.gui.Utils.mapSpecialChars; + +@DatabaseTable +public class Channel implements Parcelable { + + @DatabaseField(id = true) + String id; + private int number; + + @DatabaseField + private String name; + + @DatabaseField(index = true) + private String provider; + + @DatabaseField + private String rawAudio; + + @DatabaseField(index = true) + private String group; + + private String source; + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getGroup() { + return group; + } + + private List<AudioTrack> audio; + + public List<AudioTrack> getAudio() { + if (audio != null) { + return audio; + } + audio = AudioTrack.getAudio(rawAudio); + return audio; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getRawAudio() { + return rawAudio; + } + + public Channel(final String channelData) { + String[] words = StringUtils.splitPreserveAllTokens(channelData, + C.DATA_SEPARATOR); + this.number = Integer.valueOf(words[0].substring(1)); + if (words.length > 2) { + this.name = mapSpecialChars(words[1]); + this.provider = mapSpecialChars(words[2]); + this.id = words[3]; + this.rawAudio = words[4]; + if (words.length > 5) { + this.source = words[5]; + } else { + this.source = "Default"; + } + } else { + this.name = mapSpecialChars(words[1]); + this.id = "-1"; + this.provider = "Unknown"; + this.rawAudio = ""; + } + + } + + public Channel() { + this.number = 0; + this.name = "Unknown"; + this.provider = "Unknown"; + this.id = "Uknwon"; + this.rawAudio = ""; + } + + public Channel(Parcel in) { + this.number = in.readInt(); + this.name = in.readString(); + this.provider = in.readString(); + this.id = in.readString(); + this.rawAudio = in.readString(); + this.source = in.readString(); + } + + public boolean isGroupSeparator() { + return number == 0; + } + + public int getNumber() { + return number; + } + + public String getName() { + return name; + } + + public String getProvider() { + return provider; + } + + public String getId() { + return id; + } + + @Override + public String toString() { + final StringBuilder text = new StringBuilder(); + text.append(number); + text.append(" - "); + text.append(name); + // text.append(" : "); + // text.append(provider); + return text.toString(); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(number); + dest.writeString(name); + dest.writeString(provider); + dest.writeString(id); + dest.writeString(rawAudio); + dest.writeString(source); + + } + + public static final Parcelable.Creator<Channel> CREATOR = new Parcelable.Creator<Channel>() { + public Channel createFromParcel(Parcel in) { + return new Channel(in); + } + + public Channel[] newArray(int size) { + return new Channel[size]; + } + }; + + public boolean equals(Object o) { + if (o instanceof Channel == false) { + return false; + } + if (o == this) { + return true; + } + return ((Channel)o).getId().equals(id); + }; + + @Override + public int hashCode() { + return id.hashCode(); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Epg.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Epg.java new file mode 100644 index 0000000..bfc0c6c --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Epg.java @@ -0,0 +1,82 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.Date; + +import android.text.TextUtils; +import de.bjusystems.vdrmanager.StringUtils; +import de.bjusystems.vdrmanager.app.C; +import de.bjusystems.vdrmanager.gui.Utils; +import static de.bjusystems.vdrmanager.gui.Utils.mapSpecialChars; + +/** + * Class for EPG events + * + * @author bju + */ +public class Epg extends Event implements Timerable { + + private Timer timer; + + private TimerMatch timerMatch; + + public TimerMatch getTimerMatch() { + return timerMatch; + } + + public void setTimerMatch(TimerMatch timerMatch) { + this.timerMatch = timerMatch; + } + + public Epg(final String line) { + final String[] words = StringUtils.splitPreserveAllTokens(line, + C.DATA_SEPARATOR); + channelNumber = Long.valueOf(words[0].substring(1)); + channelName = Utils.mapSpecialChars(words[1]); + start = new Date(Long.parseLong(words[2]) * 1000); + stop = new Date(Long.parseLong(words[3]) * 1000); + title = mapSpecialChars(words[4]); + description = words.length > 5 ? mapSpecialChars(words[5]) : ""; + shortText = words.length > 6 ? mapSpecialChars(words[6]) : ""; + channelId = words.length > 7 ? mapSpecialChars(words[7]) : ""; + rawAudio = words.length > 8 ? mapSpecialChars(words[8]) : ""; + if (words.length > 9) { + String contents = words[9].trim(); + if (contents.length() > 0) { + String[] caray = contents.split(" "); + if (caray.length > 0) { + content = new int[caray.length]; + for (int i = 0; i < caray.length; ++i) { + content[i] = Integer.valueOf(caray[i]); + } + } + } + + } + if (words.length > 10) { + if (TextUtils.isEmpty(words[10]) == false) { + vps = Long.valueOf(words[10]) * 1000; + } + } + } + + public Timer getTimer() { + return timer; + } + + public void setTimer(final Timer timer) { + this.timer = timer; + timerMatch = Utils.getTimerMatch(this, timer); + } + + public TimerState getTimerState() { + if (timer == null) { + return TimerState.None; + } else { + return timer.getTimerState(); + } + } + + public Timer createTimer() { + return new Timer(this); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgCache.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgCache.java new file mode 100644 index 0000000..fd1a895 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgCache.java @@ -0,0 +1,12 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.ArrayList; +import java.util.Date; +import java.util.WeakHashMap; + +public class EpgCache { + + public static WeakHashMap<String, ArrayList<Epg>> CACHE = new WeakHashMap<String, ArrayList<Epg>>(); + + public static WeakHashMap<String, Date> NEXT_REFRESH = new WeakHashMap<String, Date>(); +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgSearchParams.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgSearchParams.java new file mode 100644 index 0000000..fbcac24 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgSearchParams.java @@ -0,0 +1,45 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.Date; + +import de.bjusystems.vdrmanager.gui.Utils; + +/** + * Class for EPG events + * @author bju + */ +public class EpgSearchParams { + + private String channelNumber; + private String title; + private Date start; + private Date end; + + public String getChannelNumber() { + return channelNumber; + } + public void setChannelNumber(final String channelNumber) { + this.channelNumber = channelNumber; + } + public String getTitle() { + return title; + } + public void setTitle(final String title) { + this.title = title; + } + public Date getStart() { + return start; + } + public void setStart(final Date start) { + this.start = start; + } + public Date getEnd() { + return end; + } + public void setEnd(final Date end) { + this.end = end; + } + public String toCommandLine() { + return Utils.unMapSpecialChars(title); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgSearchTimeValue.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgSearchTimeValue.java new file mode 100644 index 0000000..8ba9b5e --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgSearchTimeValue.java @@ -0,0 +1,56 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.Calendar; +import java.util.GregorianCalendar; + +public class EpgSearchTimeValue { + + private final int index; + private final String text; + + public EpgSearchTimeValue(final int index, final String text) { + this.index = index; + this.text = text; + } + + public EpgSearchTimeValue() { + this.index = 0; + this.text = ""; + } + + public String getText(){ + return text; + } + + public String getValue() { + switch (index) { + case -1: + return "adhoc"; + case 0: + return "now"; + case 1: + return "next"; + default: + + final String[] values = text.split(":"); + final Calendar cal = new GregorianCalendar(); + cal.set(Calendar.HOUR_OF_DAY, Integer.parseInt(values[0])); + cal.set(Calendar.MINUTE, Integer.parseInt(values[1])); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + + // next day? + final Calendar now = new GregorianCalendar(); + if (now.after(cal)) { + cal.add(Calendar.DAY_OF_MONTH, 1); + } + + return String.format("%d", cal.getTimeInMillis() / 1000); + } + } + + @Override + public String toString() { + return text; + } +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgSearchTimeValues.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgSearchTimeValues.java new file mode 100644 index 0000000..19878f3 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EpgSearchTimeValues.java @@ -0,0 +1,66 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import android.content.Context; +import de.bjusystems.vdrmanager.R; + +public class EpgSearchTimeValues { + + private final List<EpgSearchTimeValue> values = new ArrayList<EpgSearchTimeValue>(); + private final Context context; + + public EpgSearchTimeValues(final Context context) { + this.context = context; + } + + public List<EpgSearchTimeValue> getValues() { + + final Preferences prefs = Preferences.getPreferences(); + + // fixed values for now and next + values.add(new EpgSearchTimeValue(0, context.getString(R.string.epg_list_time_now))); + values.add(new EpgSearchTimeValue(1, context.getString(R.string.epg_list_time_next))); + + // get user defined values + final String userValueString = prefs.getEpgSearchTimes(); + + final String[] userValues = userValueString.split(","); + + Arrays.sort(userValues); + + for(final String userValue : userValues) { + if (userValue.contains(":")) { + values.add(new EpgSearchTimeValue(values.size(), userValue)); + } + } + + values.add(new EpgSearchTimeValue(-1, context.getString(R.string.epg_list_time_adhoc))); + return values; + } + + public void append(EpgSearchTimeValue est){ + values.add(values.size() - 1, est); + } + + public void saveValues(final List<EpgSearchTimeValue> values) { + + // get old values + final Preferences prefs = Preferences.getPreferences(); + + // add value + String newValues = ""; + for(int i = 2; i < values.size(); i++) { + final EpgSearchTimeValue value = values.get(i); + if (newValues.length() > 0) { + newValues += ","; + } + newValues += value.toString(); + } + + // save new values + prefs.setEpgSearchTimes(context, newValues); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Event.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Event.java new file mode 100644 index 0000000..bbd3181 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Event.java @@ -0,0 +1,156 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.Date; +import java.util.List; + +import android.text.TextUtils; + +/** + * Basisc class for all Events + * + * @author bju,lado + * + */ +public abstract class Event { + + protected Long channelNumber; + protected String channelId; + protected String channelName; + + protected String title; + protected String shortText; + protected String description; + protected Date start; + protected Date stop; + + protected String rawAudio; + protected int[] content = {}; + + protected long vps = 0; + + public int[] getContent() { + return content; + } + + private List<AudioTrack> audio; + + public List<AudioTrack> getAudio() { + if (audio != null) { + return audio; + } + audio = AudioTrack.getAudio(rawAudio); + return audio; + } + + public long getDuration() { + long millis = getStop().getTime() - getStart().getTime(); + return millis; + } + + public Event() { + + } + + public void setChannelNumber(Long channelNumber) { + this.channelNumber = channelNumber; + } + + public void setChannelName(String channelName) { + this.channelName = channelName; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setShortText(String shortText) { + this.shortText = shortText; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setStart(Date start) { + this.start = start; + } + + public void setStop(Date stop) { + this.stop = stop; + } + + public Event(Event event) { + channelNumber = event.getChannelNumber(); + channelId = event.getChannelId(); + channelName = event.getChannelName(); + title = event.getTitle(); + shortText = event.getShortText(); + description = event.getDescription(); + start = event.getStart(); + stop = event.getStop(); + rawAudio = event.rawAudio; + content = event.content; + vps = event.vps; + } + + public Long getChannelNumber() { + return channelNumber; + } + + public String getChannelName() { + return channelName; + } + + public String getTitle() { + return title; + } + + public String getChannelId() { + return channelId; + } + + public String getShortText() { + if (TextUtils.isEmpty(shortText) == false) { + return shortText; + } + if (TextUtils.isEmpty(description) == false) { + if (description.length() < 50) { + return description; + } + return TextUtils.substring(description, 0, 50) + "…"; + } + return ""; + } + + public String getDescription() { + return description; + } + + public Date getStart() { + return start; + } + + public Date getStop() { + return stop; + } + + public String getStreamId() { + if (channelId != null) { + return channelId; + } + return String.valueOf(channelNumber); + } + + public boolean isConflict() { + return false; + } + + public boolean hasVPS() { + return vps > 0; + } + + public long getVPS() { + return vps; + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EventContentGroup.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EventContentGroup.java new file mode 100644 index 0000000..03fcd90 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EventContentGroup.java @@ -0,0 +1,23 @@ +package de.bjusystems.vdrmanager.data; + +/** + * @author lado + * + * Based on epg.c from vdr + * + */ +public interface EventContentGroup { + + static int MovieDrama = 0x10; // + static int NewsCurrentAffairs = 0x20; // + static int Show = 0x30; // + static int Sports = 0x40; // + static int ChildrenYouth = 0x50; // + static int MusicBalletDance = 0x60; // + static int ArtsCulture = 0x70; // + static int SocialPoliticalEconomics = 0x80;// + static int EducationalScience = 0x90;// + static int LeisureHobbies = 0xA0;// + static int Special = 0xB0; // + static int UserDefined = 0xF0;// +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EventFormatter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EventFormatter.java new file mode 100644 index 0000000..6cc7151 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EventFormatter.java @@ -0,0 +1,66 @@ +package de.bjusystems.vdrmanager.data; + +import de.bjusystems.vdrmanager.gui.Utils; +import de.bjusystems.vdrmanager.utils.date.DateFormatter; + +public class EventFormatter { + + private String time; + private final String stop; + private final String date; + private final String longDate; + private final String title; + private final String shortText; + private final String description; + + public EventFormatter(final Event event) { + this(event,false); + } + /** + * @param event + * @param onlyStartTime Event Time is rendered as 'start - stop' if false + */ + public EventFormatter(final Event event, boolean onlyStartTime) { + DateFormatter formatter = new DateFormatter(event.getStart()); + this.date = formatter.getDateString(); + this.longDate = formatter.getDailyHeader(); + this.time = formatter.getTimeString(); + formatter = new DateFormatter(event.getStop()); + this.stop = formatter.getTimeString(); + if(onlyStartTime == false){ + this.time += " - " + stop; + } + this.title = Utils.mapSpecialChars(event.getTitle()); + this.shortText = Utils.mapSpecialChars(event.getShortText()); + this.description = Utils.mapSpecialChars(event.getDescription()); + } + + public String getShortText() { + return shortText; + } + + + public String getStop(){ + return stop; + } + + public String getTime() { + return time; + } + + public String getDate() { + return date; + } + + public String getLongDate() { + return longDate; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EventListItem.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EventListItem.java new file mode 100644 index 0000000..b01aee5 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/EventListItem.java @@ -0,0 +1,164 @@ +package de.bjusystems.vdrmanager.data; + +import de.bjusystems.vdrmanager.gui.Utils; + +/** + * @author lado + * + * TODO auf Event Interface umstellen und die Aufrufen an event + * delegieren. Das hier ist nicht gut. + */ +public class EventListItem extends Event { + + Event event; + + public Event getEvent() { + return event; + } + + public void setEvent(Event event) { + this.event = event; + } + + //private final Recording rec; + //private final Timer timer; + //private final Epg epg; + private final String header; + + // + // public EventListItem(final Event event){ + // if(event instanceof Recording){ + // this((Recording)event); + // } else if (event instanceof Timer){ + // this((Timer)event); + // } else { + // this((Epg)event); + // } + // throw new IllegalArgumentException("Uknown event type " + event); + // } + + + public EventListItem(final Event rec) { + super(rec); + event = rec; + this.header = null; +// this.rec = rec; + // this.epg = null; + //this.timer = null; + } +// +// public EventListItem(final Recording rec) { +// super(rec); +// event = rec; +// this.header = null; +// this.rec = rec; +// this.epg = null; +// this.timer = null; +// } +// +// public EventListItem(final Timer timer) { +// super(timer); +// event = timer; +// this.header = null; +// this.timer = timer; +// this.epg = null; +// this.rec = null; +// } +// +// public EventListItem(final Epg epg) { +// super(epg); +// event = epg; +// this.header = null; +// this.timer = null; +// this.epg = epg; +// this.rec = null; +// } +// +// @Override +// public TimerState getTimerState() { +// return event.getTimerState(); +// } + + public EventListItem(final String header) { + this.header = header; + } + + public boolean isHeader() { + return header != null; + } + +// public boolean isTimer() { +// return event instanceof Timer; +// } + + public String getHeader() { + return header; + } + +// public Timer getTimer() { +// return timer; +// } +// +// public Epg getEpg() { +// return epg; +// } +// +// public Recording getRecording() { +// return rec; +// } + + // public Event getEvent() { + // return event; + // } + // + public boolean isLive() { + return Utils.isLive(this); + } + + @Override + public String toString() { + if (isHeader()) { + return "Header: " + header; + } + + final EventFormatter formatter = new EventFormatter(this); + final StringBuilder text = new StringBuilder(); + text.append("Timer / Event: "); + text.append("Channel: ").append(getChannelNumber()); + text.append(" (").append(getChannelName()).append("), "); + text.append("Zeit: ").append(formatter.getDate()).append(" ") + .append(formatter.getTime()); + return text.toString(); + } + + + @Override + public long getDuration() { + if(event != null){ + return event.getDuration(); + } + return super.getDuration(); + } + + @Override + public String getChannelId() { + if(event != null){ + return event.getChannelId(); + } + return null; + } + + @Override + public String getStreamId() { + if(event == null){ + return null; + } + return event.getStreamId(); + } +// +// @Override +// public Timer getTimer() { +// return event.getTimer(); +// } + +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/FetchEditTextPreference.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/FetchEditTextPreference.java new file mode 100644 index 0000000..17a93b7 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/FetchEditTextPreference.java @@ -0,0 +1,94 @@ +package de.bjusystems.vdrmanager.data; + +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences.Editor; +import android.preference.DialogPreference; +import android.util.AttributeSet; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageButton; +import de.bjusystems.vdrmanager.R; + +public class FetchEditTextPreference extends DialogPreference{ + + private EditText mEditText; + + private String initialValue; + + public String getInitialValue() { + return initialValue; + } + + public void setInitialValue(String initialValue) { + this.initialValue = initialValue; + } + + public EditText getmEditText() { + return mEditText; + } + + public void setEditText(String text) { + this.mEditText.setText(text); + } + + private ImageButton mButton; + + private String mText; + //private CharSequence mCompoundButtonText; + private View.OnClickListener mCompoundButtonCallback; + + public FetchEditTextPreference(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + setDialogLayoutResource(R.layout.fetch_preference); + } + + public FetchEditTextPreference(Context context, AttributeSet attrs) { + super(context, attrs); + setDialogLayoutResource(R.layout.fetch_preference); + } + + @Override + protected View onCreateDialogView() { + View root = super.onCreateDialogView(); + mEditText = (EditText) root.findViewById(R.id.edit); + mButton = (ImageButton) root.findViewById(R.id.button); + return root; + } + + public void setText(String text) { + mText = text; + } + + public String getText() { + return mText; + } + + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: // User clicked OK! + mText = mEditText.getText().toString(); + callChangeListener(mText); + Editor editor = (Editor) getEditor(); + editor.putString(getKey(), mText); + editor.commit(); + break; + } + super.onClick(dialog, which); + } + + @Override + protected void onBindDialogView(View view) { + mEditText.setText(mText); + //mButton.setText(mCompoundButtonText); + + // Set a callback to our button. + mButton.setOnClickListener(mCompoundButtonCallback); + } + + public void setCompoundButtonListener(View.OnClickListener callback) { + mCompoundButtonCallback = callback; + } +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/HasAudio.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/HasAudio.java new file mode 100644 index 0000000..10d9fd9 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/HasAudio.java @@ -0,0 +1,5 @@ +package de.bjusystems.vdrmanager.data; + +public class HasAudio { + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/MenuActionHandler.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/MenuActionHandler.java new file mode 100644 index 0000000..b1f1d2e --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/MenuActionHandler.java @@ -0,0 +1,16 @@ +package de.bjusystems.vdrmanager.data; + +import android.content.Context; + + +/** + * Handler for menu actions + * @author bju + */ +public interface MenuActionHandler { + + /** + * Execute the action + */ + void executeAction(Context context); +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/OrmLiteBasePreferenceActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/OrmLiteBasePreferenceActivity.java new file mode 100644 index 0000000..eae8302 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/OrmLiteBasePreferenceActivity.java @@ -0,0 +1,89 @@ +package de.bjusystems.vdrmanager.data; + +import android.content.Context; +import android.os.Bundle; +import android.preference.PreferenceActivity; + +import com.j256.ormlite.android.apptools.OpenHelperManager; +import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; +import com.j256.ormlite.support.ConnectionSource; + +public abstract class OrmLiteBasePreferenceActivity<H extends OrmLiteSqliteOpenHelper> extends PreferenceActivity { + + + private volatile H helper; + private volatile boolean created = false; + private volatile boolean destroyed = false; + + /** + * Get a helper for this action. + */ + public H getHelper() { + if (helper == null) { + if (!created) { + throw new IllegalStateException("A call has not been made to onCreate() yet so the helper is null"); + } else if (destroyed) { + throw new IllegalStateException( + "A call to onDestroy has already been made and the helper cannot be used after that point"); + } else { + throw new IllegalStateException("Helper is null for some unknown reason"); + } + } else { + return helper; + } + } + + /** + * Get a connection source for this action. + */ + public ConnectionSource getConnectionSource() { + return getHelper().getConnectionSource(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + if (helper == null) { + helper = getHelperInternal(this); + created = true; + } + super.onCreate(savedInstanceState); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + releaseHelper(helper); + destroyed = true; + } + + /** + * This is called internally by the class to populate the helper object instance. This should not be called directly + * by client code unless you know what you are doing. Use {@link #getHelper()} to get a helper instance. If you are + * managing your own helper creation, override this method to supply this activity with a helper instance. + * + * <p> + * <b> NOTE: </b> If you override this method, you most likely will need to override the + * {@link #releaseHelper(OrmLiteSqliteOpenHelper)} method as well. + * </p> + */ + protected H getHelperInternal(Context context) { + @SuppressWarnings({ "unchecked", "deprecation" }) + H newHelper = (H) OpenHelperManager.getHelper(context); + return newHelper; + } + + /** + * Release the helper instance created in {@link #getHelperInternal(Context)}. You most likely will not need to call + * this directly since {@link #onDestroy()} does it for you. + * + * <p> + * <b> NOTE: </b> If you override this method, you most likely will need to override the + * {@link #getHelperInternal(Context)} method as well. + * </p> + */ + protected void releaseHelper(H helper) { + OpenHelperManager.releaseHelper(); + this.helper = null; + } + +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/P.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/P.java new file mode 100644 index 0000000..b3e9918 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/P.java @@ -0,0 +1,11 @@ +package de.bjusystems.vdrmanager.data; + +public interface P { + + public static String CHANNELS_LAST_ORDER = "CHANNELS_LAST_ORDER"; + + public static String EPG_LAST_SORT = "EPG_LAST_SORT"; + + public static String CHANNELS_LAST_ORDER_REVERSE = "CHANNELS_LAST_ORDER_ORDER"; + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Preferences.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Preferences.java new file mode 100644 index 0000000..a73a786 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Preferences.java @@ -0,0 +1,783 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.List; +import java.util.Locale; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.text.TextUtils; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.StringUtils; +import de.bjusystems.vdrmanager.data.db.DBAccess; + +/** + * Class for all preferences + * + * @author bju, lado + */ +public class Preferences { + + public static final String DEFAULT_LANGUAGE_VALUE = "DEFAULT"; + + public static final String PREFERENCE_FILE_NAME = "VDR-Manager"; + + private static Vdr current; + + public static void set(Context context, String key, int value) { + final SharedPreferences sharedPrefs = getSharedPreferences(context); + sharedPrefs.edit().putInt(key, value).commit(); + } + + public static void set(Context context, String key, boolean value) { + final SharedPreferences sharedPrefs = getSharedPreferences(context); + sharedPrefs.edit().putBoolean(key, value).commit(); + } + + public static int get(Context context, String key, int defValue) { + final SharedPreferences sharedPrefs = getSharedPreferences(context); + return sharedPrefs.getInt(key, defValue); + + } + + public static String get(Context context, String key, String defValue) { + final SharedPreferences sharedPrefs = getSharedPreferences(context); + return sharedPrefs.getString(key, defValue); + + } + + public static boolean get(Context context, String key, boolean defValue) { + final SharedPreferences sharedPrefs = getSharedPreferences(context); + return sharedPrefs.getBoolean(key, defValue); + + } + + public static void setCurrentVdr(Context context, Vdr vdr) { + current = vdr; + final SharedPreferences sharedPrefs = getSharedPreferences(context); + sharedPrefs + .edit() + .putInt(context.getString(R.string.current_vdr_id_key), + current != null ? current.getId() : -1).commit(); + } + + public Vdr getCurrentVdr() { + return current; + } + + public int getCurrentVdrContext(Context context) { + return getInteger(context, R.string.current_vdr_id_key, -1); + } + + /** + * user defined epg search times + */ + private String epgSearchTimes; + /** + * format times AM/PM or 24H + * + * @since 0.2 + */ + private boolean use24hFormat; + + /** + * Quits the app on back button + */ + private boolean quiteOnBackButton = true; + + /** + * Show IMDB buttons, where possible (e.g. EPG Details) + */ + private boolean showImdbButton = true; + + /** + * Show OMDB button in epg details + */ + private boolean showOmdbButton = false; + + /** + * Show TMDb button in epg details + */ + private boolean showTmdbButton = false; + + private int maxRecentChannels = 10; + + public int getMaxRecentChannels() { + return maxRecentChannels; + } + + public boolean isShowTmdbButton() { + return showTmdbButton; + } + + public boolean isShowOmdbButton() { + return showOmdbButton; + } + + /** + * On Which imdb site to search? + */ + private String imdbUrl = "akas.imdb.com"; + + private boolean showChannelNumbers = false; + + public String getEncoding() { + return getCurrentVdr().getEncoding(); + } + + public int getConnectionTimeout() { + return getCurrentVdr().getConnectionTimeout(); + } + + public int getReadTimeout() { + return getCurrentVdr().getReadTimeout(); + } + + public int getTimeout() { + return getCurrentVdr().getTimeout(); + } + + public String getImdbUrl() { + return imdbUrl; + } + + public void setImdbUrl(String imdbUrl) { + this.imdbUrl = imdbUrl; + } + + /** + * @return whether to shwo imdb button + */ + public boolean isShowImdbButton() { + return showImdbButton; + } + + public String getStreamingUsername() { + return getCurrentVdr().getStreamingUsername(); + } + + public String getStreamingPassword() { + return getCurrentVdr().getStreamingPassword(); + } + + /** + * Properties singleton + */ + private static Preferences thePrefs; + + /** + * Whether to send Packets to the custom broadcast address. It is used, if + * the address ist not empty + * + * @return + * @since 0.2 + */ + public String getWolCustomBroadcast() { + return getCurrentVdr().getWolCustomBroadcast(); + } + + /** + * Getter for use24hFormat + * + * @return + * @since 0.2 + */ + public boolean isUse24hFormat() { + return use24hFormat; + } + + /** + * Checks for connect using SSL + * + * @return true, if use SSL connections + */ + public boolean isSecure() { + return getCurrentVdr().isSecure(); + } + + /** + * Retrieves the channel filtering mode + * + * @return true, if channels will be filtered + */ + public boolean isFilterChannels() { + return getCurrentVdr().isFilterChannels(); + } + + /** + * Last channel to receive + * + * @return channel number + */ + public String getChannels() { + return isFilterChannels() ? getCurrentVdr().getChannelFilter() : ""; + } + + /** + * Gets the host or IP address + * + * @return host + */ + public String getHost() { + return getCurrentVdr().getHost(); + } + + /** + * Gets the port + * + * @return port + */ + public int getPort() { + return getCurrentVdr().getPort(); + } + + /** + * Gets the port + * + * @return port + */ + public int getSvdrpPort() { + return getCurrentVdr().getSvdrpPort(); + } + + /** + * Gets password + * + * @return password + */ + public String getPassword() { + String pwd = getCurrentVdr().getPassword(); + if (pwd == null) { + return StringUtils.EMPTY_STRING; + } + return pwd; + } + + /** + * Checks for enables remote wakeup + * + * @return true, if remote wakeup is enabled + */ + public boolean isWakeupEnabled() { + return getCurrentVdr().isWakeupEnabled(); + } + + /** + * Gets the URL for the wakeup request + * + * @return wakeup url + */ + public String getWakeupUrl() { + return getCurrentVdr().getWakeupUrl(); + } + + /** + * Gets the user for the wakeup url + * + * @return user name + */ + public String getWakeupUser() { + return getCurrentVdr().getWakeupUser(); + } + + /** + * Gets the password for the wakeup url + * + * @return password + */ + public String getWakeupPassword() { + return getCurrentVdr().getWakeupPassword(); + } + + /** + * Checks for enabled alive check + * + * @return true, if enabled + */ + public boolean isAliveCheckEnabled() { + return getCurrentVdr().isAliveCheckEnabled(); + } + + public boolean isEnableRecStream() { + return getCurrentVdr().isEnableRecStreaming(); + } + + public int getLivePort() { + return getCurrentVdr().getLivePort(); + } + + /** + * Gets the time between alive checks + * + * @return time in seconds + */ + public int getAliveCheckInterval() { + return getCurrentVdr().getAliveCheckInterval(); + } + + /** + * Gets the buffer before the event start + * + * @return pre event buffer + */ + public int getTimerPreMargin() { + return getCurrentVdr().getTimerPreMargin(); + } + + /** + * Gets the buffer after the event stop + * + * @return post event buffer + */ + public int getTimerPostMargin() { + return getCurrentVdr().getTimerPostMargin(); + } + + /** + * Gets the default priority + * + * @return default priority + */ + public int getTimerDefaultPriority() { + return getCurrentVdr().getTimerDefaultPriority(); + } + + /** + * Gets the default lifetime + * + * @return default lifetime + */ + public int getTimerDefaultLifetime() { + return getCurrentVdr().getTimerDefaultLifetime(); + } + + /** + * Gets the time values for the epg search + * + * @return + */ + public String getEpgSearchTimes() { + return epgSearchTimes; + } + + /** + * gets the MAC Address of the vdr host + * + * @return + * @since 0.2 + */ + public String getVdrMac() { + return getCurrentVdr().getMac(); + } + + /** + * Gets the selection which wakeup method to use + * + * @return + * @since 0.2 + */ + public String getWakeupMethod() { + return getCurrentVdr().getWakeupMethod(); + } + + /** + * Getter for streaming port + * + * @return + * @since 02. + */ + public int getStreamPort() { + return getCurrentVdr().getStreamPort(); + } + + /** + * Getter for selected streaming format + * + * @return + * @since 0.2 + */ + public String getStreamFormat() { + return getCurrentVdr().getStreamFormat(); + } + + public int getSmarttvewebPort() { + return getCurrentVdr().getSmarttvwebPort(); + } + + public String getSmarttvewebType() { + return getCurrentVdr().getSmarttvwebType(); + } + + /** + * Sets the time values for the epg search + * + * @param epgSearchTimes new time values + */ + public void setEpgSearchTimes(final Context context, + final String epgSearchTimes) { + + final SharedPreferences prefs = getSharedPreferences(context); + final SharedPreferences.Editor editor = prefs.edit(); + editor.putString(context.getString(R.string.epg_search_times_key), + epgSearchTimes); + editor.commit(); + + // reload + init(context); + } + + /** + * Gets the name for the file which preferences will be saved into + * + * @param context Context + * @return filename + */ + public static String getPreferenceFile(final Context context) { + return PREFERENCE_FILE_NAME; + } + + /** + * Show Channel Numbers in the overviews + * + * @return + * @since 0.2 + */ + public boolean isShowChannelNumbers() { + return showChannelNumbers; + } + + /** + * getter + * + * @return + */ + public boolean isEnableRemux() { + return getCurrentVdr().isEnableRemux(); + } + + /** + * getter + * + * @return + */ + public String getRemuxCommand() { + return getCurrentVdr().getRemuxCommand(); + } + + /** + * getter + * + * @return + */ + public String getRemuxParameter() { + return getCurrentVdr().getRemuxParameter(); + } + + /** + * getter + * + * @return + */ + public boolean isQuiteOnBackButton() { + return quiteOnBackButton; + } + + /** + * Gets the previous loaded preferences + * + * @return preferences + */ + public static Preferences getPreferences() { + return thePrefs; + } + + public String getRecStreamMethod() { + return getCurrentVdr().getRecStreamMethod(); + } + + /** + * Gets the previous loaded preferences, same as getPreferences(); + * + * @return + */ + public static Preferences get() { + return thePrefs; + } + + private static void initInternal(final Context context) { + + final Preferences prefs = new Preferences(); + + prefs.epgSearchTimes = getString(context, + R.string.epg_search_times_key, ""); + + prefs.use24hFormat = getBoolean(context, + R.string.gui_enable_24h_format_key, true); + + prefs.showChannelNumbers = getBoolean(context, + R.string.gui_channels_show_channel_numbers_key, false); + + prefs.quiteOnBackButton = getBoolean(context, + R.string.qui_quit_on_back_key, true); + + prefs.showImdbButton = getBoolean(context, + R.string.qui_show_imdb_button_key, true); + + prefs.showOmdbButton = getBoolean(context, + R.string.qui_show_omdb_button_key, false); + + prefs.showTmdbButton = getBoolean(context, + R.string.qui_show_tmdb_button_key, false); + + prefs.imdbUrl = getString(context, R.string.qui_imdb_url_key, + "akas.imdb.com"); + + prefs.maxRecentChannels = getInt(context, + R.string.gui_max_recent_channels_key, 10); + + + thePrefs = prefs; + } + + public static void reset() { + thePrefs = null; + } + + public static void reloadVDR(Context context) { + if (current == null) { + return; + } + DBAccess.get(context).getVdrDAO().refresh(current); + } + + public static boolean initVDR(final Context context) { + + if (current != null) { + return true; + } + + int id = getInteger(context, R.string.current_vdr_id_key, -1); + + Vdr vdr = null; + if (id != -1) { + vdr = DBAccess.get(context).getVdrDAO().queryForId(id); + } + + setCurrentVdr(context, vdr); + + if (vdr != null) { + return true; + } + + List<Vdr> list = DBAccess.get(context).getVdrDAO().queryForAll(); + if (list != null && list.isEmpty() == false) { + vdr = list.get(0); + setCurrentVdr(context, vdr); + return true; + } + + return initFromOldVersion(context); + // Intent intent = new Intent(); + // intent.setClass(context, VdrListActivity.class); + // intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + // intent.putExtra(Intents.EMPTY_CONFIG, Boolean.TRUE); + // context.startActivity(intent); + // Toast.makeText(context, R.string.no_vdr, Toast.LENGTH_SHORT).show(); + } + + /** + * Loads all preferences + * + * @param context Context + * @return Preferences + */ + public static void init(final Context context) { + // if (thePrefs != null) { + // return; + // } + synchronized (Preferences.class) { + // if (thePrefs != null) { + // return; + // } + initInternal(context); + setLocale(context); + } + } + + private static boolean initFromOldVersion(Context context) { + + Vdr vdr = new Vdr(); + + String host = getString(context, R.string.vdr_host_key, null); + if (host == null) { + return false; + } + vdr.setHost(host); + vdr.setName("Default"); + vdr.setPort(getInt(context, R.string.vdr_port_key, 6420)); + vdr.setPassword(getString(context, R.string.vdr_password_key, "")); + vdr.setSecure(getBoolean(context, R.string.vdr_ssl_key, false)); + vdr.setStreamPort(getInt(context, R.string.vdr_stream_port, 3000)); + vdr.setStreamFormat(getString(context, R.string.vdr_stream_format, "TS")); + + vdr.setAliveCheckEnabled(getBoolean(context, + R.string.alive_check_enabled_key, false)); + vdr.setAliveCheckInterval(getInt(context, + R.string.alive_check_interval_key, 60)); + + vdr.setChannelFilter(getString(context, + R.string.channel_filter_last_key, "").replace(" ", "")); + vdr.setFilterChannels(getBoolean(context, + R.string.channel_filter_filter_key, false)); + vdr.setWakeupEnabled(getBoolean(context, R.string.wakeup_enabled_key, + false)); + vdr.setWakeupUrl(getString(context, R.string.wakeup_url_key, "")); + vdr.setWakeupUser(getString(context, R.string.wakeup_user_key, "")); + vdr.setWakeupPassword(getString(context, R.string.wakeup_password_key, + "")); + + vdr.setTimerPreMargin(getInt(context, + R.string.timer_pre_start_buffer_key, 5)); + vdr.setTimerPostMargin(getInt(context, + R.string.timer_post_end_buffer_key, 30)); + + vdr.setTimerDefaultPriority(getInt(context, + R.string.timer_default_priority_key, 99)); + + vdr.setTimerDefaultLifetime(getInt(context, + R.string.timer_default_lifetime_key, 99)); + + vdr.setEpgSearchTimes(getString(context, R.string.epg_search_times_key, + "")); + + vdr.setMac(getString(context, R.string.wakeup_wol_mac_key, "")); + + vdr.setWakeupMethod(getString(context, R.string.wakeup_method_key, + "url")); + + vdr.setWolCustomBroadcast(getString(context, + R.string.wakeup_wol_custom_broadcast_key, "")); + + vdr.setConnectionTimeout(getInt(context, R.string.vdr_conntimeout_key, + 10)); + vdr.setReadTimeout(getInt(context, R.string.vdr_readtimeout_key, 10)); + vdr.setTimeout(getInt(context, R.string.vdr_timeout_key, 120)); + + vdr.setStreamingUsername(getString(context, + R.string.streaming_username_key, "")); + + vdr.setStreamingPassword(getString(context, + R.string.streaming_password_key, "")); + + vdr.setEncoding(getString(context, R.string.vdr_encoding_key, "utf-8")); + + if (DBAccess.get(context).getVdrDAO().create(vdr) != 1) { + return false; + } + + setCurrentVdr(context, vdr); + + return true; + } + + /** + * Gets the persistent preferences + * + * @param context Context + * @return preferences + */ + public static SharedPreferences getSharedPreferences(final Context context) { + return context.getSharedPreferences(getPreferenceFile(context), + Context.MODE_PRIVATE); + } + + /** + * Helper for retrieving integer values from preferences + * + * @param context Context + * @param resId ressource id of the preferences name + * @param defValue default value + * @return value or the default value if not defined + */ + private static int getInt(final Context context, final int resId, + final int defValue) { + final String value = getString(context, resId, String.valueOf(defValue)); + if (TextUtils.isEmpty(value) || !TextUtils.isDigitsOnly(value)) { + return 0; + } + return Integer.parseInt(value); + } + + /** + * Helper for retrieving boolean values from preferences + * + * @param context Context + * @param resId ressource id of the preferences name + * @param defValue default value + * @return value or the default value if not defined + */ + private static boolean getBoolean(final Context context, final int resId, + final boolean defValue) { + final SharedPreferences sharedPrefs = getSharedPreferences(context); + return sharedPrefs.getBoolean(context.getString(resId), defValue); + } + + /** + * Helper for retrieving string values from preferences + * + * @param context Context + * @param resId ressource id of the preferences name + * @param defValue default value + * @return value or the default value if not defined + */ + private static String getString(final Context context, final int resId, + final String defValue) { + final SharedPreferences sharedPrefs = getSharedPreferences(context); + return sharedPrefs.getString(context.getString(resId), defValue); + } + + private static int getInteger(final Context context, final int resId, + final int defValue) { + final SharedPreferences sharedPrefs = getSharedPreferences(context); + return sharedPrefs.getInt(context.getString(resId), defValue); + } + + public String getTimeFormat() { + if (isUse24hFormat()) { + return "HH:mm"; + } + return "h:mm a"; + } + + /** + * Set locale read from preferences to context. + * + * @param context {@link Context} + */ + public static void setLocale(final Context context) { + String lc = getString(context, R.string.gui_custom_locale_key, + DEFAULT_LANGUAGE_VALUE); + Locale locale = null; + // TODO lado this is very bad. + if (lc.equals(DEFAULT_LANGUAGE_VALUE)) { + String lang = Locale.getDefault().toString(); + if (lang.startsWith("de")) { + locale = Locale.GERMAN; + } else if (lang.startsWith("it")) { + locale = Locale.ITALIAN; + } else { + locale = Locale.ENGLISH; + } + } else { + locale = new Locale(lc); + } + final Configuration config = new Configuration(); + config.locale = locale; + context.getResources().updateConfiguration(config, null); + } + + public boolean isRemoteEnabled() { + return getCurrentVdr().isEnableRemote(); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/RecentChannelDAO.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/RecentChannelDAO.java new file mode 100644 index 0000000..d8a8c41 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/RecentChannelDAO.java @@ -0,0 +1,86 @@ +package de.bjusystems.vdrmanager.data; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import android.util.Log; + +import com.j256.ormlite.dao.BaseDaoImpl; +import com.j256.ormlite.support.ConnectionSource; + +public class RecentChannelDAO extends BaseDaoImpl<RecenteChannel, String> { + + private static final String TAG = RecentChannelDAO.class.getName(); + + public RecentChannelDAO(ConnectionSource connectionSource) + throws SQLException { + super(connectionSource, RecenteChannel.class); + } + + public List<RecenteChannel> loadByLastAccess(long max) { + try { + return queryBuilder().orderBy("lastAccess", false).limit(max).query(); + } catch (SQLException e) { + Log.w(TAG, e.getMessage(), e); + return new ArrayList<RecenteChannel>(0); + } + } + + public List<RecenteChannel> loadByRecentUse(long max) { + try { + return queryBuilder().orderBy("count", false).limit(max).query(); + } catch (SQLException e) { + Log.w(TAG, e.getMessage(), e); + return new ArrayList<RecenteChannel>(0); + } + } + + public RecenteChannel queryForId(String id) { + try { + return super.queryForId(id); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public List<Channel> getRecentChannels(Map<String, Channel> all, List<RecenteChannel> recents){ + + List<Channel> filtered = new ArrayList<Channel>(); + for(RecenteChannel rc : recents){ + Channel c = all.get(rc.getChannelId()); + if(c == null){ + try { + delete(rc); + } catch (SQLException e) { + Log.w(TAG, e.getMessage(), e); + } + } else { + filtered.add(c); + } + } + return filtered; + } + + public CreateOrUpdateStatus createOrUpdate(RecenteChannel data) { + try { + return super.createOrUpdate(data); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + public void hit(String channelId) { + RecenteChannel rc = queryForId(channelId); + + if (rc == null) { + rc = new RecenteChannel(); + rc.setChannelId(channelId); + } + rc.touch(); + rc.incUse(); + createOrUpdate(rc); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/RecenteChannel.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/RecenteChannel.java new file mode 100644 index 0000000..b747537 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/RecenteChannel.java @@ -0,0 +1,59 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.Date; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; + +@DatabaseTable(daoClass=RecentChannelDAO.class) +public class RecenteChannel { + + @DatabaseField(id=true, generatedId = false) + private String channelId; + + @DatabaseField + private long count = 0; + + @DatabaseField + private Date lastAccess; + + public Date getLastAccess() { + return lastAccess; + } + + public void setLastAccess(Date lastAccess) { + this.lastAccess = lastAccess; + } + + public long getCount() { + return count; + } + + public void setCount(long count) { + this.count = count; + } + + public String getChannelId() { + return channelId; + } + + public void setChannelId(String channelId) { + this.channelId = channelId; + } + + public void incUse(){ + count++; + } + + public void touch(){ + lastAccess = new Date(); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("[id="+channelId +", count="+count+", lastAccess=" + lastAccess); + return super.toString(); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Recording.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Recording.java new file mode 100644 index 0000000..d1ae085 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Recording.java @@ -0,0 +1,257 @@ +package de.bjusystems.vdrmanager.data; + +import static de.bjusystems.vdrmanager.gui.Utils.mapSpecialChars; + +import java.util.Date; + +import de.bjusystems.vdrmanager.StringUtils; +import de.bjusystems.vdrmanager.app.C; +import de.bjusystems.vdrmanager.gui.Utils; + +public class Recording extends Event { + + public static String ROOT_FOLDER = ""; + + public static final String FOLDERDELIMCHAR = "~"; + + public Recording(String line) { + String[] words = StringUtils.splitPreserveAllTokens(line, + C.DATA_SEPARATOR); + int idx = 0; + index = Integer.valueOf(words[idx++]); + start = new Date(Long.parseLong(words[idx++]) * 1000); + stop = new Date(Long.parseLong(words[idx++]) * 1000); + channelName = mapSpecialChars(words[idx++]); + title = mapSpecialChars(words[idx++]); + shortText = mapSpecialChars(words[idx++]); + description = mapSpecialChars(words[idx++]); + fileName = mapSpecialChars(words[idx++]); + try { + fileSize = Integer.valueOf(words[idx++]); + } catch (NumberFormatException ex) { + + /************** TEMPORARY TO FIX THE BUG UNTIL Server's 0.13 */ + + int offset = 0; + int count = 0; + while(offset != -1){ + offset = line.indexOf(":", offset + 1); + count++; + if(count == 5){ + words = StringUtils.splitPreserveAllTokens(line.substring(0, offset) + Utils.unMapSpecialChars(":") + line.substring(offset+1), + C.DATA_SEPARATOR); + break; + } + } + + idx = 0; + index = Integer.valueOf(words[idx++]); + start = new Date(Long.parseLong(words[idx++]) * 1000); + stop = new Date(Long.parseLong(words[idx++]) * 1000); + channelName = mapSpecialChars(words[idx++]); + title = mapSpecialChars(words[idx++]); + shortText = mapSpecialChars(words[idx++]); + description = mapSpecialChars(words[idx++]); + fileName = mapSpecialChars(words[idx++]); + fileSize = Integer.valueOf(words[idx++]); + + if (idx < words.length) { + channelId = words[idx++]; + } + if (idx < words.length) { + realDuration = Long.parseLong(words[idx++]) * 1000; + } + + if (idx < words.length) { + devInode = mapSpecialChars(words[idx++]); + } + + if (idx < words.length) { // timer + String data = words[idx++]; + if (data != null && data.length() > 0) { + timerStopTime = new Date(Long.parseLong(data) * 1000L); + } + } + + if (idx < words.length) { // name + String titleRaw = mapSpecialChars(words[idx++]); + int idxdel = titleRaw.lastIndexOf(FOLDERDELIMCHAR); + if (idxdel == -1) { + title = titleRaw; + folder = ROOT_FOLDER; + } else { + title = titleRaw.substring(idxdel + 1); + String foldersRaw = titleRaw.substring(0, idxdel); + folder = foldersRaw; + + } + } else { + folder = ROOT_FOLDER; + } + + if (idx < words.length) { + if (words[idx++].equals("1")) { + neww = true; + } + } + if (title.charAt(0) == '%') { + cut = true; + title = title.substring(1); + } + return; + } + + if (idx < words.length) { + channelId = words[idx++]; + } + if (idx < words.length) { + realDuration = Long.parseLong(words[idx++]) * 1000; + } + + if (idx < words.length) { + devInode = mapSpecialChars(words[idx++]); + } + + if (idx < words.length) { // timer + String data = words[idx++]; + if (data != null && data.length() > 0) { + timerStopTime = new Date(Long.parseLong(data) * 1000L); + } + } + + if (idx < words.length) { // name + String titleRaw = mapSpecialChars(words[idx++]); + int idxdel = titleRaw.lastIndexOf(FOLDERDELIMCHAR); + if (idxdel == -1) { + title = titleRaw; + folder = ROOT_FOLDER; + } else { + title = titleRaw.substring(idxdel + 1); + String foldersRaw = titleRaw.substring(0, idxdel); + folder = foldersRaw; + + } + } else { + folder = ROOT_FOLDER; + } + + if (idx < words.length) { + if (words[idx++].equals("1")) { + neww = true; + } + } + if (title.charAt(0) == '%') { + cut = true; + title = title.substring(1); + } + + } + + private String folder; + + private String fileName; + + private int fileSize; + + private int index; + + private long realDuration = -1; + + private String devInode = null; + + private boolean cut = false; + + private boolean neww = false; + + /** + * If it is not null, recording is on going or will be on going until this + * date; + */ + private Date timerStopTime = null; + + public Date getTimerStopTime() { + return timerStopTime; + } + + public void setTimerStopTime(Date timerStopTime) { + this.timerStopTime = timerStopTime; + } + + public String getDevInode() { + return devInode; + } + + public void setDevInode(String devInode) { + this.devInode = devInode; + } + + /** + * in millis + * + * @return + */ + public long getRealDuration() { + return realDuration; + } + + public long getDuration() { + if (timerStopTime != null) { + return timerStopTime.getTime() - start.getTime(); + } + + if (realDuration != -1) { + return realDuration; + } + return super.getDuration(); + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public int getFileSize() { + return fileSize; + } + + public void setFileSize(int fileSize) { + this.fileSize = fileSize; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String toCommandLine() { + return String.valueOf(index); + } + + @Override + public String toString() { + return title; + } + + public String getFolder() { + return folder; + } + + public void setFolder(String folder) { + this.folder = folder; + } + + public boolean isCut() { + return cut; + } + + public boolean isNeww() { + return neww; + } + +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/RecordingListItem.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/RecordingListItem.java new file mode 100644 index 0000000..8395364 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/RecordingListItem.java @@ -0,0 +1,45 @@ +package de.bjusystems.vdrmanager.data; + +import de.bjusystems.vdrmanager.gui.RecordingDir; + + +public class RecordingListItem extends EventListItem { + + public RecordingDir folder; + + //public Integer count = 0; + + public RecordingListItem(Recording rec) { + super(rec); + } + + public RecordingListItem(String dailyHeader) { + super(dailyHeader); + } + + + + @Override + public String getTitle() { + if(isFolder()){ + return folder.getName(); + } + return super.getTitle(); + } + + public boolean isFolder() { + + return folder != null; + + } + + @Override + public String getHeader() { + if (isFolder()) { + return folder.getName(); + } else { + return super.getHeader(); + } + } + +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Timer.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Timer.java new file mode 100644 index 0000000..7782dca --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Timer.java @@ -0,0 +1,276 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; + +import android.text.TextUtils; +import de.bjusystems.vdrmanager.StringUtils; +import de.bjusystems.vdrmanager.app.C; +import de.bjusystems.vdrmanager.gui.Utils; + +/** + * Class for timer data + * + * @author bju + */ +public class Timer extends Event implements Timerable { + + // tfActive = 0x0001, + // tfInstant = 0x0002, + // tfVps = 0x0004, + // tfRecording = 0x0008, + // tfAll = 0xFFFF, + private static final int ACTIVE = 1; + private static final int INSTANT = 2; + private static final int VPS = 4; + private static final int RECORDING = 8; + + private int number; + private int flags; + private int priority; + private int lifetime; + private String weekdays = "-------"; + private boolean conflict; + + + public void setPriority(int priority) { + this.priority = priority; + } + + public void setLifetime(int lifetime) { + this.lifetime = lifetime; + } + + public String getWeekdays() { + return weekdays; + } + + public void setWeekdays(String weekdays) { + this.weekdays = weekdays; + } + + /** + * Constructs a timer from SvdrpHelper result line + * + * @param timerData + * result line + */ + public Timer(final String timerData) { + + final String[] values = StringUtils.splitPreserveAllTokens(timerData, + C.DATA_SEPARATOR); + + // number + this.number = Integer.valueOf(values[0].substring(1)); + + // flags, channel number and channel name + this.flags = Integer.valueOf(values[1]); + this.channelNumber = Long.valueOf(values[2]); + this.channelName = Utils.mapSpecialChars(values[3]); + + // get start and stop + this.start = new Date(Long.parseLong(values[4]) * 1000); + this.stop = new Date(Long.parseLong(values[5]) * 1000); + + // priority and lifetime + this.priority = Integer.valueOf(values[6]); + this.lifetime = Integer.valueOf(values[7]); + + // title and description + this.title = Utils.mapSpecialChars(values[8]); + + this.description = values.length > 9 ? values[9] : "";// aux + + // 10 and 11 are optional if there where event with this timer + this.shortText = values.length > 10 ? Utils.mapSpecialChars(values[10]) + : ""; + + if (values.length > 11) { + this.description = values[11]; // if real description, set it + } + + if (values.length > 12) { + this.channelId = values[12]; + } + + if (values.length > 13) { + this.weekdays = values[13]; + } + + if (values.length > 14) { + this.conflict = values[14].equals("1"); + } + + description = Utils.mapSpecialChars(description); + + if (values.length > 15) { + if (TextUtils.isEmpty(values[15]) == false) { + vps = Long.valueOf(values[15]) * 1000; + } else if (isVps()) { + vps = start.getTime(); + } + + } + + } + + public Timer copy() { + Timer t = new Timer(this); + t.flags = flags; + t.number = number; + t.priority = priority; + t.lifetime = lifetime; + t.start = new Date(start.getTime()); + t.stop = new Date(stop.getTime()); + t.title = title; + t.weekdays = weekdays; + t.conflict = conflict; + t.vps = vps; + return t; + } + + public Timer(final Event event) { + + this.number = 0; + this.flags = ACTIVE; + this.channelNumber = event.getChannelNumber(); + this.channelName = event.getChannelName(); + this.channelId = event.getChannelId(); + + this.start = event.getStart(); + + this.stop = event.getStop(); + + this.title = event.getTitle(); + if (Utils.isSerie(event.getContent())) { + if (TextUtils.isEmpty(event.getShortText()) == false) { + this.title += "~" + event.getShortText(); + } + } + this.description = event.getDescription(); + this.vps = event.getVPS(); + } + + public boolean isRecurring() { + return weekdays.equals("-------") == false; + } + + public String toCommandLine() { + + final StringBuilder line = new StringBuilder(); + + // line.append(number).append(":"); + line.append(flags).append(":"); + line.append(channelNumber).append(":"); + + final Calendar cal = Calendar.getInstance(); + if(isVps() == true){ + cal.setTimeInMillis(vps); + } else { + cal.setTime(start); + } + + cal.setTimeZone(TimeZone.getTimeZone(Preferences.get().getCurrentVdr() + .getServerTimeZone())); + + line.append((weekdays.equals("-------") == false ? weekdays + "@" : "") + + String.format("%04d-%02d-%02d:", cal.get(Calendar.YEAR), + cal.get(Calendar.MONTH) + 1, + cal.get(Calendar.DAY_OF_MONTH))); + line.append(String.format("%02d%02d:", cal.get(Calendar.HOUR_OF_DAY), + cal.get(Calendar.MINUTE))); + + cal.setTime(stop); + line.append(String.format("%02d%02d:", cal.get(Calendar.HOUR_OF_DAY), + cal.get(Calendar.MINUTE))); + + line.append(priority).append(":"); + line.append(lifetime).append(":"); + line.append(Utils.unMapSpecialChars(title)); + return line.toString(); + } + + public int getNumber() { + return number; + } + + public int getPriority() { + return priority; + } + + public int getLifetime() { + return lifetime; + } + + public TimerState getTimerState() { + if (isEnabled()) { + if (isRecording()) { + return TimerState.Recording; + } + return TimerState.Active; + } + return TimerState.Inactive; + } + + public boolean isEnabled() { + return (flags & ACTIVE) == ACTIVE; + } + + public boolean isInstant() { + return (flags & INSTANT) == INSTANT; + } + + public boolean isVps() { + return (flags & VPS) == VPS; + } + + public boolean isRecording() { + return (flags & RECORDING) == RECORDING; + } + + public boolean isConflict() { + return conflict; + } + + public void setStart(final Date start) { + this.start = start; + } + + public void setStop(final Date stop) { + this.stop = stop; + } + + public void setVps(boolean useVps) { + if (useVps) { + flags = flags | VPS; + } else { + flags = flags & ~VPS; + } + } + + public void setEnabled(final boolean enabled) { + if (enabled) { + flags = flags | ACTIVE; + } else { + flags = flags & ~ACTIVE; + } + } + + public Timer getTimer() { + return this; + } + + public Timer createTimer() { + return new Timer(this); + } + + @Override + public TimerMatch getTimerMatch() { + + if (isConflict()) + return TimerMatch.Conflict; + + return TimerMatch.Full; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/TimerMatch.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/TimerMatch.java new file mode 100644 index 0000000..b8126ab --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/TimerMatch.java @@ -0,0 +1,8 @@ +package de.bjusystems.vdrmanager.data; + +public enum TimerMatch { + Full, // + Begin, // + End, // + Conflict, // +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Timerable.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Timerable.java new file mode 100644 index 0000000..aa371ed --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Timerable.java @@ -0,0 +1,22 @@ +package de.bjusystems.vdrmanager.data; + + +public interface Timerable { + + public enum TimerState { + None, + Active, + Inactive, + Recording, + Recorded + ; + } + + public Timer createTimer(); + + public abstract Timer getTimer(); + + public TimerState getTimerState(); + + public TimerMatch getTimerMatch(); +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Vdr.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Vdr.java new file mode 100644 index 0000000..b7960d3 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/Vdr.java @@ -0,0 +1,732 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +import com.j256.ormlite.field.DatabaseField; +import com.j256.ormlite.table.DatabaseTable; + +@DatabaseTable +public class Vdr { + + /** + * + */ + @DatabaseField(columnName = "_id", generatedId = true) + private Integer id; + + @DatabaseField(columnName = "name") + private String name = "VDR"; + + /** + * Use secure channel + */ + @DatabaseField(defaultValue = "false") + private boolean secure; + + /** host name or ip */ + @DatabaseField + private String host = "0.0.0.0"; + + /** port */ + @DatabaseField + private Integer port = 6420; + + /** Password */ + @DatabaseField + private String password = ""; + + /** should channels be filtered? */ + @DatabaseField + private boolean filterChannels = false; + + /** last channel to retrieve */ + @DatabaseField + private String channelFilter = ""; + + /** Enable remote wakeup */ + @DatabaseField + private boolean wakeupEnabled = false; + + /** URL of the wakeup script */ + @DatabaseField + private String wakeupUrl = ""; + + /** User for wakeup */ + @DatabaseField + private String wakeupUser = ""; + + /** Password for wakeup */ + @DatabaseField + private String wakeupPassword = ""; + + /** + * vdr mac for wol + * + * @since 0.2 + */ + @DatabaseField + private String mac = ""; + + /** + * which wakeup method to use + * + * @since 0.2 + * + */ + @DatabaseField + private String wakeupMethod = "wol"; + + /** Check for running VDR is enabled */ + @DatabaseField + private boolean aliveCheckEnabled; + + /** Intervall for alive test */ + @DatabaseField + private Integer aliveCheckInterval; + + /** Buffer before event */ + @DatabaseField + private int timerPreMargin = 5; + + /** Buffer after event */ + @DatabaseField + private int timerPostMargin = 30; + + /** Default priority */ + @DatabaseField + private int timerDefaultPriority = 50; + + /** Default lifetime */ + @DatabaseField + private int timerDefaultLifetime = 99; + + /** user defined epg search times */ + @DatabaseField + private String epgSearchTimes = ""; + + /** + * Which port to use for streaming + * + * @since 0.2 + */ + @DatabaseField + private int streamPort = 3000; + + /** + * Which format to use for streaming + * + * @since 0.2 + */ + @DatabaseField + private String streamFormat = "TS"; + + /** + * Do not send broadcasts, send directly to the host (router problem) + * + * @since 0.2 + */ + @DatabaseField + private String wolCustomBroadcast = "255.255.255.255"; + + /** + * Use remux ? + */ + @DatabaseField + private boolean enableRemux = false; + + /** + * Remux command + */ + @DatabaseField + private String remuxCommand = "EXT"; + + /** + * Remux command Parameter + */ + @DatabaseField + private String remuxParameter = "QUALITY=DSL1000"; + + @DatabaseField + private String encoding = "utf-8"; + + /** + * Connection timeout + */ + @DatabaseField + private int connectionTimeout = 10; + + /** + * Read Timeout + */ + @DatabaseField + private int readTimeout = 10; + + /** + * Timeout for a whole command run + */ + @DatabaseField + private int timeout = 60; + + @DatabaseField + private String streamingUsername = ""; + + @DatabaseField + private String streamingPassword = ""; + + @DatabaseField + private int livePort = 8008; + + @DatabaseField + private String recStreamMethod = "vdr-live"; + + @DatabaseField(columnName="smarttvwebPort") + private int smarttvwebPort = 8000; + + @DatabaseField(columnName="smarttvwebType") + private String smarttvwebType="ts"; + + @DatabaseField + private boolean enableRecStreaming = false; + + @DatabaseField(columnName = "stz") + private String serverTimeZone = "Europe/Berlin"; + + @DatabaseField(columnName="svdrpPort") + private int svdrpPort = 6419; + + @DatabaseField(columnName="enableRemote") + private boolean enableRemote = true; + + public String getServerTimeZone() { + return serverTimeZone; + } + + public void setServerTimeZone(String serverTimeZone) { + this.serverTimeZone = serverTimeZone; + } + + public String getRecStreamMethod() { + return recStreamMethod; + } + + public void setRecStreamMethod(String recStreamMethod) { + this.recStreamMethod = recStreamMethod; + } + + public int getLivePort() { + return livePort; + } + + public void setLivePort(int livePort) { + this.livePort = livePort; + } + + public boolean isEnableRecStreaming() { + return enableRecStreaming; + } + + public void setEnableRecStreaming(boolean enableRecStreaming) { + this.enableRecStreaming = enableRecStreaming; + } + + public String getStreamingPassword() { + return streamingPassword; + } + + public void setStreamingPassword(String streamingPassword) { + this.streamingPassword = streamingPassword; + } + + public String getStreamingUsername() { + return streamingUsername; + } + + public void setStreamingUsername(String streamingUsername) { + this.streamingUsername = streamingUsername; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isSecure() { + return secure; + } + + public void setSecure(boolean secure) { + this.secure = secure; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isFilterChannels() { + return filterChannels; + } + + public void setFilterChannels(boolean filterChannels) { + this.filterChannels = filterChannels; + } + + public String getChannelFilter() { + return channelFilter; + } + + public void setChannelFilter(String channelFilter) { + this.channelFilter = channelFilter; + } + + public boolean isWakeupEnabled() { + return wakeupEnabled; + } + + public void setWakeupEnabled(boolean wakeupEnabled) { + this.wakeupEnabled = wakeupEnabled; + } + + public String getWakeupUrl() { + return wakeupUrl; + } + + public void setWakeupUrl(String wakeupUrl) { + this.wakeupUrl = wakeupUrl; + } + + public String getWakeupUser() { + return wakeupUser; + } + + public void setWakeupUser(String wakeupUser) { + this.wakeupUser = wakeupUser; + } + + public String getWakeupPassword() { + return wakeupPassword; + } + + public void setWakeupPassword(String wakeupPassword) { + this.wakeupPassword = wakeupPassword; + } + + public String getMac() { + return mac; + } + + public void setMac(String mac) { + this.mac = mac; + } + + public String getWakeupMethod() { + return wakeupMethod; + } + + public void setWakeupMethod(String wakeupMethod) { + this.wakeupMethod = wakeupMethod; + } + + public boolean isAliveCheckEnabled() { + return aliveCheckEnabled; + } + + public void setAliveCheckEnabled(boolean aliveCheckEnabled) { + this.aliveCheckEnabled = aliveCheckEnabled; + } + + public int getAliveCheckInterval() { + return aliveCheckInterval; + } + + public void setAliveCheckInterval(int aliveCheckInterval) { + this.aliveCheckInterval = aliveCheckInterval; + } + + public int getTimerPreMargin() { + return timerPreMargin; + } + + public void setTimerPreMargin(int timerPreMargin) { + this.timerPreMargin = timerPreMargin; + } + + public int getTimerPostMargin() { + return timerPostMargin; + } + + public void setTimerPostMargin(int timerPostMargin) { + this.timerPostMargin = timerPostMargin; + } + + public int getTimerDefaultPriority() { + return timerDefaultPriority; + } + + public void setTimerDefaultPriority(int timerDefaultPriority) { + this.timerDefaultPriority = timerDefaultPriority; + } + + public int getTimerDefaultLifetime() { + return timerDefaultLifetime; + } + + public void setTimerDefaultLifetime(int timerDefaultLifetime) { + this.timerDefaultLifetime = timerDefaultLifetime; + } + + public String getEpgSearchTimes() { + return epgSearchTimes; + } + + public void setEpgSearchTimes(String epgSearchTimes) { + this.epgSearchTimes = epgSearchTimes; + } + + public int getStreamPort() { + return streamPort; + } + + public void setStreamPort(int streamPort) { + this.streamPort = streamPort; + } + + public String getStreamFormat() { + return streamFormat; + } + + public void setStreamFormat(String streamFormat) { + this.streamFormat = streamFormat; + } + + public String getWolCustomBroadcast() { + return wolCustomBroadcast; + } + + public void setWolCustomBroadcast(String wolCustomBroadcast) { + this.wolCustomBroadcast = wolCustomBroadcast; + } + + public boolean isEnableRemux() { + return enableRemux; + } + + public void setEnableRemux(boolean enableRemux) { + this.enableRemux = enableRemux; + } + + public String getRemuxCommand() { + return remuxCommand; + } + + public void setRemuxCommand(String remuxCommand) { + this.remuxCommand = remuxCommand; + } + + public String getRemuxParameter() { + return remuxParameter; + } + + public void setRemuxParameter(String remuxParameter) { + this.remuxParameter = remuxParameter; + } + + public String getEncoding() { + return encoding; + } + + public void setEncoding(String encoding) { + this.encoding = encoding; + } + + public int getConnectionTimeout() { + return connectionTimeout; + } + + public void setConnectionTimeout(int connectionTimeout) { + this.connectionTimeout = connectionTimeout; + } + + public int getReadTimeout() { + return readTimeout; + } + + public void setReadTimeout(int readTimeout) { + this.readTimeout = readTimeout; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + private static <T> T get(Map<String, Object> map, String key) { + return get(map, key, null); + } + + private static <T> T get(Map<String, Object> map, String key, Object def) { + if (map.containsKey(key)) { + return (T) map.get(key); + } + return (T) def; + } + + private static Integer getInteger(Map<String, Object> map, String key) { + return getInteger(map, key, 0); + } + + private static Integer getInteger(Map<String, Object> map, String key, + Integer def) { + if (map.containsKey(key) == false) { + return def; + } + + Object obj = get(map, key); + if (obj instanceof Integer) { + return (Integer) obj; + } + + try { + return Integer.valueOf(String.valueOf(obj)); + } catch (NumberFormatException nfe) { + return def; + } + } + + // private static Integer getInteger(Map<String, Object> map, String key) { + // if (map.containsKey(key) == false) { + // return Integer.valueOf(0); + // } + + // Object obj = get(map, key); + // if (obj instanceof Integer) { + // return (Integer) obj; + // } + // return Integer.valueOf(String.valueOf(obj)); + // } + + private static Boolean getBoolean(Map<String, Object> map, String key) { + return getBoolean(map, key, false); + } + + private static Boolean getBoolean(Map<String, Object> map, String key, + boolean defValue) { + if (map.containsKey(key) == false) { + return defValue; + } + Object obj = get(map, key); + if (obj instanceof Boolean) { + return (Boolean) obj; + } + return Boolean.valueOf(String.valueOf(obj)); + } + + public HashMap<String, Object> toMap() { + HashMap<String, Object> map = new HashMap<String, Object>(); + map.put("vdr_name", name); + map.put("vdr_host", host); + map.put("vdr_port", port); + map.put("vdr_password", password); + map.put("vdr_secure", secure); + + map.put("limit_channels", filterChannels); + map.put("last_channel", channelFilter); + + map.put("key_wakeup_enabled", wakeupEnabled); + map.put("key_wakeup_url", wakeupUrl); + map.put("key_wakeup_user", wakeupUser); + map.put("key_wakeup_password", wakeupPassword); + map.put("key_wakeup_method", wakeupMethod); + map.put("key_wol_custom_broadcast", wolCustomBroadcast); + map.put("key_wakeup_wol_mac", mac); + + map.put("key_conntimeout_key", connectionTimeout); + map.put("key_vdr_readtimeout", readTimeout); + map.put("key_vdr_timeout", timeout); + + map.put("timer_pre_start_buffer", timerPreMargin); + map.put("timer_post_end_buffer", timerPostMargin); + map.put("timer_default_priority", timerDefaultPriority); + map.put("timer_default_lifetime", timerDefaultLifetime); + + map.put("streamingport", streamPort); + map.put("key_streaming_password", streamingPassword); + map.put("key_streaming_username", streamingUsername); + map.put("key_vdr_encoding", encoding); + map.put("livetv_streamformat", streamFormat); + map.put("remux_command", remuxCommand); + map.put("remux_parameter", remuxParameter); + map.put("remux_enable", enableRemux); + map.put("key_rec_stream_enable", enableRecStreaming); + map.put("key_live_port", livePort); + map.put("key_recstream_method", recStreamMethod); + map.put("key_timezone", serverTimeZone); + + + map.put("key_smarttvweb_port", smarttvwebPort); + map.put("key_smarttvweb_recstream_method", smarttvwebType); + map.put("key_remote_enable", enableRemote); + map.put("key_svdrp_port", svdrpPort); + return map; + } + + public void set(Map<String, Object> map) { + init(map); +/* + name = get(map, "vdr_name"); + host = get(map, "vdr_host"); + port = getInteger(map, "vdr_port"); + password = get(map, "vdr_password"); + secure = getBoolean(map, "vdr_secure"); + + filterChannels = getBoolean(map, "limit_channels"); + channelFilter = get(map, "last_channel"); + + wakeupEnabled = getBoolean(map, "key_wakeup_enabled"); + wakeupUrl = get(map, "key_wakeup_url"); + wakeupUser = get(map, "key_wakeup_user"); + wakeupPassword = get(map, "key_wakeup_password"); + wakeupMethod = get(map, "key_wakeup_method"); + wolCustomBroadcast = get(map, "key_wol_custom_broadcast"); + mac = get(map, "key_wakeup_wol_mac"); + + connectionTimeout = getInteger(map, "key_conntimeout_key"); + readTimeout = getInteger(map, "key_vdr_readtimeout"); + timeout = getInteger(map, "key_vdr_timeout"); + + timerPreMargin = getInteger(map, "timer_pre_start_buffer" + ); + timerPostMargin = getInteger(map, "timer_post_end_buffer"); + timerDefaultPriority = getInteger(map, "timer_default_priority"); + timerDefaultLifetime = getInteger(map, "timer_default_lifetime"); + + streamPort = getInteger(map, "streamingport"); + streamingPassword = get(map, "key_streaming_password"); + streamingUsername = get(map, "key_streaming_username"); + encoding = get(map, "key_vdr_encoding"); + streamFormat = get(map, "livetv_streamformat"); + remuxCommand = get(map, "remux_command"); + remuxParameter = get(map, "remux_parameter"); + enableRemux = getBoolean(map, "remux_enable"); + + enableRecStreaming = getBoolean(map, "key_rec_stream_enable"); + livePort = getInteger(map, "key_live_port"); + recStreamMethod = get(map, "key_recstream_method"); + */ + } + + public void init(Map<String, Object> map) { + name = get(map, "vdr_name", "VDR"); + host = get(map, "vdr_host", "0.0.0.0"); + port = getInteger(map, "vdr_port", 6420); + password = get(map, "vdr_password", ""); + secure = getBoolean(map, "vdr_secure"); + + filterChannels = getBoolean(map, "limit_channels", true); + channelFilter = get(map, "last_channel", ""); + + wakeupEnabled = getBoolean(map, "key_wakeup_enabled", false); + wakeupUrl = get(map, "key_wakeup_url", ""); + wakeupUser = get(map, "key_wakeup_user", ""); + wakeupPassword = get(map, "key_wakeup_password", ""); + wakeupMethod = get(map, "key_wakeup_method", "wol"); + wolCustomBroadcast = get(map, "key_wol_custom_broadcast", ""); + mac = get(map, "key_wakeup_wol_mac", ""); + + connectionTimeout = getInteger(map, "key_conntimeout_key", 10); + readTimeout = getInteger(map, "key_vdr_readtimeout", 10); + timeout = getInteger(map, "key_vdr_timeout", 60); + + timerPreMargin = getInteger(map, "timer_pre_start_buffer", 5); + timerPostMargin = getInteger(map, "timer_post_end_buffer", 30); + timerDefaultPriority = getInteger(map, "timer_default_priority", 50); + timerDefaultLifetime = getInteger(map, "timer_default_lifetime",99); + + streamPort = getInteger(map, "streamingport", 3000); + streamingPassword = get(map, "key_streaming_password", ""); + streamingUsername = get(map, "key_streaming_username", ""); + encoding = get(map, "key_vdr_encoding", "utf-8"); + streamFormat = get(map, "livetv_streamformat", "TS"); + remuxCommand = get(map, "remux_command", "EXT"); + remuxParameter = get(map, "remux_parameter", ""); + enableRemux = getBoolean(map, "remux_enable"); + + enableRecStreaming = getBoolean(map, "key_rec_stream_enable"); + livePort = getInteger(map, "key_live_port", 8008); + recStreamMethod = get(map, "key_recstream_method", "vdr-live"); + serverTimeZone = get(map, "key_timezone", TimeZone.getDefault().getID()); + + smarttvwebPort= getInteger(map, "key_smarttvweb_port", 8000); + smarttvwebType= get(map, "key_smarttvweb_recstream_method", "progressive"); + enableRemote = getBoolean(map, "key_remote_enable", true); + svdrpPort = getInteger(map, "key_svdrp_port", 6419); + } + + public int getSmarttvwebPort() { + return smarttvwebPort; + } + + public void setSmarttvwebPort(int smarttvwebPort) { + this.smarttvwebPort = smarttvwebPort; + } + + public String getSmarttvwebType() { + return smarttvwebType; + } + + public void setSmarttvwebType(String smarttvwebType) { + this.smarttvwebType = smarttvwebType; + } + + public int getSvdrpPort() { + return svdrpPort; + } + + public void setSvdrpPort(int svdrpPort) { + this.svdrpPort = svdrpPort; + } + + public boolean isEnableRemote() { + return enableRemote; + } + + public void setEnableRemote(boolean enableRemote) { + this.enableRemote = enableRemote; + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/VdrSharedPreferences.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/VdrSharedPreferences.java new file mode 100644 index 0000000..bb0ad77 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/VdrSharedPreferences.java @@ -0,0 +1,218 @@ +package de.bjusystems.vdrmanager.data; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import android.content.SharedPreferences; + +import com.j256.ormlite.dao.Dao.CreateOrUpdateStatus; +import com.j256.ormlite.dao.RuntimeExceptionDao; + +@Deprecated +public class VdrSharedPreferences implements SharedPreferences { + + private static final String EMPTY_STRING = ""; + + public int commits = 0; + + public RuntimeExceptionDao<Vdr, Integer> dao; + + private Vdr instance; + + public Vdr getInstance() { + return instance; + } + + public void setInstance(Vdr instance) { + this.instance = instance; + map.putAll(instance.toMap()); + } + + protected List<OnSharedPreferenceChangeListener> listeners = new LinkedList<OnSharedPreferenceChangeListener>(); + + Map<String, Object> map = new HashMap<String, Object>(); + + public VdrSharedPreferences() { + + } + + // public VdrSharedPreferences(Vdr vdr) { + + // } + + public boolean contains(String key) { + return map.containsKey(key); + } + + public Editor edit() { + return new Editor(); + } + + public Map<String, Object> getAll() { + return map; + } + + public boolean getBoolean(String key, boolean defValue) { + return get(key, defValue); + } + + public float getFloat(String key, float defValue) { + return get(key, defValue); + } + + public int getInt(String key, int defValue) { + String val = get(key, String.valueOf(defValue)); + try { + return Integer.valueOf(val); + } catch (Exception ex) { + return defValue; + } + } + + public long getLong(String key, long defValue) { + String val = get(key, String.valueOf(defValue)); + try { + return Long.valueOf(val); + } catch (Exception ex) { + return defValue; + } + } + + public <T> T get(String key, T defValue) { + if (map.containsKey(key)) { + return (T) map.get(key); + } + return defValue; + } + + public String getString(String key, String defValue) { + Object obj = get(key, defValue); + if (obj == null) { + return EMPTY_STRING; + } + return String.valueOf(obj); + } + + public Set<String> getStringSet(String arg0, Set<String> arg1) { + return null; + } + + public void registerOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + listeners.add(listener); + } + + public void unregisterOnSharedPreferenceChangeListener( + OnSharedPreferenceChangeListener listener) { + listeners.remove(listener); + } + + /** + * @author lado + * + */ + public class Editor implements SharedPreferences.Editor { + + Map<String, Object> modified = new HashMap<String, Object>(); + + private boolean clear = false; + + public SharedPreferences.Editor clear() { + clear = true; + modified.clear(); + return this; + } + + public boolean commit() { + + if (instance == null) { + map.putAll(modified); + return true; + } + + instance.set(modified); + + CreateOrUpdateStatus custatus = dao.createOrUpdate(instance); + + boolean status = custatus.isCreated() || custatus.isUpdated(); + + if (status == false) + return false; + + map.putAll(modified); + + ++commits; + + // and update any listeners + for (String key : modified.keySet()) { + for (OnSharedPreferenceChangeListener listener : listeners) { + listener.onSharedPreferenceChanged( + VdrSharedPreferences.this, key); + } + } + + modified.clear(); + + return true; + } + + public android.content.SharedPreferences.Editor put(String key, + Object value) { + synchronized (this) { + modified.put(key, value); + return this; + } + } + + public android.content.SharedPreferences.Editor putBoolean(String key, + boolean value) { + return put(key, value); + } + + public android.content.SharedPreferences.Editor putFloat(String key, + float value) { + return put(key, value); + } + + public android.content.SharedPreferences.Editor putInt(String key, + int value) { + return put(key, value); + } + + public android.content.SharedPreferences.Editor putLong(String key, + long value) { + return put(key, value); + } + + public android.content.SharedPreferences.Editor putString(String key, + String value) { + return put(key, value); + } + + public android.content.SharedPreferences.Editor remove(String key) { + synchronized (this) { + modified.remove(key); + return this; + } + } + + public void apply() { + commit(); + } + + public android.content.SharedPreferences.Editor putStringSet( + String key, Set<String> values) { + synchronized (this) { + modified.put(key, (values == null) ? null + : new HashSet<String>(values)); + return this; + } + } + + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/WakeupState.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/WakeupState.java new file mode 100644 index 0000000..f7917d8 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/WakeupState.java @@ -0,0 +1,36 @@ +package de.bjusystems.vdrmanager.data; + +public class WakeupState { + + public static WakeupState OK = new WakeupState(0); + public static WakeupState FAILED = new WakeupState(1); + public static WakeupState ERROR = new WakeupState(2); + + private final int value; + private static WakeupState state; + + private WakeupState(final int value) { + this.value = value; + } + + public static WakeupState getState() { + return state; + } + + public static void setState(final WakeupState state) { + WakeupState.state = state; + } + + @Override + public boolean equals(final Object o) { + if (!(o instanceof WakeupState)) { + return false; + } + return this.value == ((WakeupState)o).value; + } + + @Override + public int hashCode() { + return Integer.valueOf(value).hashCode(); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/db/DBAccess.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/db/DBAccess.java new file mode 100644 index 0000000..f854204 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/db/DBAccess.java @@ -0,0 +1,241 @@ +package de.bjusystems.vdrmanager.data.db; + +import java.sql.SQLException; +import java.util.TimeZone; + +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import com.j256.ormlite.android.apptools.OpenHelperManager; +import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper; +import com.j256.ormlite.dao.RuntimeExceptionDao; +import com.j256.ormlite.support.ConnectionSource; +import com.j256.ormlite.table.TableUtils; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.RecentChannelDAO; +import de.bjusystems.vdrmanager.data.RecenteChannel; +import de.bjusystems.vdrmanager.data.Vdr; + +/** + * Database helper class used to manage the creation and upgrading of your + * database. This class also usually provides the DAOs used by the other + * classes. + */ +public class DBAccess extends OrmLiteSqliteOpenHelper { + + public static final String TAG = DBAccess.class.getName(); + // name of the database file for your application -- change to something + // appropriate for your app + public static final String DATABASE_NAME = "vdrmanager.db"; + // any time you make changes to your database objects, you may have to + // increase the database version + // Version 3 since 0.6 + private static final int DATABASE_VERSION = 5; + + private RuntimeExceptionDao<Vdr, Integer> vdrDAO = null; + + private RecentChannelDAO recentChannelDAO = null; + + public static String getDataBaseFile() { + return "/data/data/de.bjusystems.vdrmanager/databases/" + DATABASE_NAME; + } + + public DBAccess(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION, + R.raw.ormlite_config); + } + + /** + * This is called when the database is first created. Usually you should + * call createTable statements here to create the tables that will store + * your data. + */ + @Override + public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) { + try { + Log.i(DBAccess.class.getName(), "onCreate"); + TableUtils.createTable(connectionSource, Vdr.class); + TableUtils.createTable(connectionSource, RecenteChannel.class); + } catch (SQLException e) { + Log.e(DBAccess.class.getName(), "Can't create database", e); + throw new RuntimeException(e); + } + } + + /** + * This is called when your application is upgraded and it has a higher + * version number. This allows you to adjust the various data to match the + * new version number. + */ + @Override + public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, + int oldVersion, int newVersion) { + try { + + // Log.i(DBAccess.class.getName(), "onUpgrade"); + // TableUtils.dropTable(connectionSource, Vdr.class, true); + // after we drop the old databases, we create the new ones + // onCreate(db, connectionSource); + + if (oldVersion < 3) { + TableUtils.createTable(connectionSource, RecenteChannel.class); + getVdrDAO() + .executeRaw( + "ALTER TABLE `vdr` ADD COLUMN stz varchar;"); + + String tz = TimeZone.getDefault().getID(); + getVdrDAO() + .executeRaw( + "UPDATE `vdr` set stz = ?", tz); + } + + if(oldVersion < 4){ + getVdrDAO() + .executeRaw( + "ALTER TABLE `vdr` ADD COLUMN smarttvwebType varchar;"); + getVdrDAO() + .executeRaw( + "ALTER TABLE `vdr` ADD COLUMN smarttvwebPort int;"); + + getVdrDAO() + .executeRaw( + "UPDATE `vdr` set smarttvwebPort = ?", "8000"); + getVdrDAO() + .executeRaw( + "UPDATE `vdr` set smarttvwebType = ?", "progressive"); + + + } + + if(oldVersion < 5){ + getVdrDAO() + .executeRaw( + "ALTER TABLE `vdr` ADD COLUMN enableRemote boolean;"); + getVdrDAO() + .executeRaw( + "ALTER TABLE `vdr` ADD COLUMN svdrpPort int;"); + + getVdrDAO() + .executeRaw( + "UPDATE `vdr` set enableRemote = ?", "1"); + getVdrDAO() + .executeRaw( + "UPDATE `vdr` set svdrpPort = ?", "6419"); + + } + + } catch (SQLException e) { + Log.e(DBAccess.class.getName(), "Can't drop databases", e); + throw new RuntimeException(e); + } + } + + /** + * Close the database connections and clear any cached DAOs. + */ + @Override + public void close() { + super.close(); + vdrDAO = null; + } + + static volatile DBAccess helper; + static volatile boolean created = false; + static volatile boolean destroyed = false; + + /** + * Get a helper for this action. + */ + public static DBAccess get(Context ctx) { + + if (helper == null) { + helper = getHelperInternal(ctx); + created = true; + } + + if (helper == null) { + if (!created) { + throw new IllegalStateException( + "A call has not been made to onCreate() yet so the helper is null"); + } else if (destroyed) { + throw new IllegalStateException( + "A call to onDestroy has already been made and the helper cannot be used after that point"); + } else { + throw new IllegalStateException( + "Helper is null for some unknown reason"); + } + } else { + return helper; + } + } + + /** + * Get a connection source for this action. + */ + public ConnectionSource getConnectionSource(Context ctx) { + return get(ctx).getConnectionSource(); + } + + /** + * This is called internally by the class to populate the helper object + * instance. This should not be called directly by client code unless you + * know what you are doing. Use {@link #getHelper()} to get a helper + * instance. If you are managing your own helper creation, override this + * method to supply this activity with a helper instance. + * + * <p> + * <b> NOTE: </b> If you override this method, you most likely will need to + * override the {@link #releaseHelper(OrmLiteSqliteOpenHelper)} method as + * well. + * </p> + */ + protected static DBAccess getHelperInternal(Context context) { + @SuppressWarnings({ "unchecked", "deprecation" }) + DBAccess newHelper = (DBAccess) OpenHelperManager.getHelper(context, + DBAccess.class); + logger.trace("{}: got new helper {} from OpenHelperManager", "", + newHelper); + return newHelper; + } + + /** + * Release the helper instance created in + * {@link #getHelperInternal(Context)}. You most likely will not need to + * call this directly since {@link #onDestroy()} does it for you. + * + * <p> + * <b> NOTE: </b> If you override this method, you most likely will need to + * override the {@link #getHelperInternal(Context)} method as well. + * </p> + */ + protected void releaseHelper(DBAccess helper) { + OpenHelperManager.releaseHelper(); + logger.trace("{}: helper {} was released, set to null", this, helper); + DBAccess.helper = null; + } + + public RuntimeExceptionDao<Vdr, Integer> getVdrDAO() { + if (vdrDAO == null) { + vdrDAO = getRuntimeExceptionDao(Vdr.class); + } + return vdrDAO; + } + + public RecentChannelDAO getRecentChannelDAO() { + if (recentChannelDAO == null) { + try { + recentChannelDAO = getDao(RecenteChannel.class); + } catch (SQLException e) { + throw new RuntimeException( + "Could not create RuntimeExcepitionDao for class " + + RecenteChannel.class, e); + } + + } + + return recentChannelDAO; + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/db/EPGSearchSuggestionsProvider.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/db/EPGSearchSuggestionsProvider.java new file mode 100644 index 0000000..3ff6162 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/data/db/EPGSearchSuggestionsProvider.java @@ -0,0 +1,23 @@ +package de.bjusystems.vdrmanager.data.db; + +import android.content.SearchRecentSuggestionsProvider; + +public class EPGSearchSuggestionsProvider extends + SearchRecentSuggestionsProvider { + + + public final static String AUTHORITY = EPGSearchSuggestionsProvider.class.getName(); + + public final static int MODE = DATABASE_MODE_QUERIES;// | DATABASE_MODE_2LINES; + + public EPGSearchSuggestionsProvider() { + setupSuggestions(AUTHORITY, MODE); + } + + + public static enum SecondLine { + EPG, + EPG_SEARCH, + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/About.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/About.java new file mode 100644 index 0000000..4d6290c --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/About.java @@ -0,0 +1,35 @@ +package de.bjusystems.vdrmanager.gui; + +import de.bjusystems.vdrmanager.R; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.pm.PackageInfo; +import android.text.SpannableString; +import android.text.util.Linkify; + +public class About { + + static AlertDialog about = null; + + public static void show(Activity activity){ + if(about == null){ + String vi = ""; + PackageInfo pi = Utils.getPackageInfo(activity); + if(pi != null){ + vi = "v"+pi.versionName; + } + //View view = activity.getLayoutInflater().inflate(R.layout.about, null); + final SpannableString s = new SpannableString(activity.getString(R.string.about_text,vi)); + Linkify.addLinks(s, Linkify.ALL); + about = new AlertDialog.Builder(activity) + .setTitle(R.string.about_title) + .setMessage(s) + .setPositiveButton(android.R.string.ok, null) + .setCancelable(false) + .create(); + } + if(!activity.isFinishing()){ + about.show(); + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseActivity.java new file mode 100644 index 0000000..9e0ccc4 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseActivity.java @@ -0,0 +1,651 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.List; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.support.v4.app.ActionBarDrawerToggle; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.MenuCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarActivity; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.ViewFlipper; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.remote.RemoteActivity; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpEvent; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpException; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpExceptionListener; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpFinishedListener; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpListener; + +public abstract class BaseActivity<Result, T extends ListView> extends + ActionBarActivity implements OnClickListener, SvdrpListener, + SvdrpExceptionListener, SvdrpFinishedListener<Result> { + + public static final String TAG = BaseActivity.class.getName(); + + public static final int MENU_GROUP_REFRESH = 99; + + public static final int MENU_REFRESH = 99; + + protected T listView; + + protected ViewFlipper flipper; + + private Button retry; + + private ProgressDialog progress; + + // protected SvdrpProgressDialog progress; + + private CharSequence mDrawerTitle; + private DrawerLayout mDrawerLayout; + private ListView mDrawerList; + private ActionBarDrawerToggle mDrawerToggle; + private String[] mTitles; + + abstract protected String getWindowTitle(); + + abstract protected int getMainLayout(); + + protected void noInternetConnection() { + alert(R.string.no_internet_connection); + } + + abstract protected boolean displayingResults(); + + /** + * When using the ActionBarDrawerToggle, you must call it during + * onPostCreate() and onConfigurationChanged()... + */ + + @Override + protected void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + // Sync the toggle state after onRestoreInstanceState has occurred. + mDrawerToggle.syncState(); + } + + protected boolean isForceRefresh() { + if (forceRefresh == false) { + return false; + } + forceRefresh = false; + return true; + } + + protected boolean forceRefresh = false; + + protected void switchNoConnection() { + if (flipper == null) { + say(R.string.no_connection); + return; + } + + if (displayingResults()) { + say(R.string.no_connection); + } else { + flipper.setDisplayedChild(1); + } + } + + protected CertificateProblemDialog getCertificateProblemDialog() { + return new CertificateProblemDialog(this); + } + + @Override + public void onConfigurationChanged(final Configuration newConfig) { + Preferences.setLocale(this); + super.onConfigurationChanged(newConfig); + mDrawerToggle.onConfigurationChanged(newConfig); + } + + @Override + protected void onResume() { + Preferences.setLocale(this); + // Preferences.init(this); + super.onResume(); + } + + protected void initFlipper() { + this.flipper = (ViewFlipper) findViewById(R.id.flipper); + retry = (Button) findViewById(R.id.retry_button); + retry.setOnClickListener(this); + } + + @Override + public void onClick(final View v) { + if (v.getId() == R.id.retry_button) { + retry(); + } + } + + // + // protected void updateWindowTitle(int topic, int subtopic) { + // String title; + // title = getString(topic); + // if (subtopic != -1) { + // title += " > " + getString(subtopic); + // } + // setTitle(title); + // } + // + // protected void updateWindowTitle(String topic, String subtopic) { + // String title = topic; + // if (subtopic != null) { + // title += " > " + subtopic; + // } + // setTitle(title); + // } + + abstract protected int getListNavigationIndex(); + + public static final int LIST_NAVIGATION_CHANNELS = 0; + public static final int LIST_NAVIGATION_EPG_BY_TIME = 1; + public static final int LIST_NAVIGATION_EPG_BY_CHANNEL = 2; + public static final int LIST_NAVIGATION_RECORDINGS = 3; + public static final int LIST_NAVIGATION_TIMERS = 4; + public static final int LIST_NAVIGATION_REMOTE = 5; + + protected boolean hasListNavigation() { + return true; + } + + protected void initListNavigation() { + + if (hasListNavigation() == false) { + return; + } + + // getSupportActionBar().setDisplayShowTitleEnabled(false); + // + // getSupportActionBar().setNavigationMode( + // getSupportActionBar().NAVIGATION_MODE_LIST); + // + // final ArrayAdapter<CharSequence> mSpinnerAdapter = ArrayAdapter + // .createFromResource(this, R.array.navigation_array, + // android.R.layout.simple_spinner_dropdown_item); + // + // getSupportActionBar().setListNavigationCallbacks(mSpinnerAdapter, + // new OnNavigationListener() { + // + // private boolean firstHit = true; + // + // @Override + // public boolean onNavigationItemSelected( + // final int itemPosition, final long itemId) { + // + // if (firstHit == true) { + // firstHit = false; + // return false; + // } + // switch (itemPosition) { + // + // case LIST_NAVIGATION_CHANNELS: { + // startActivity(ChannelListActivity.class); + // return true; + // } + // case LIST_NAVIGATION_EPG_BY_TIME: { + // startActivity(TimeEpgListActivity.class); + // return true; + // } + // + // case LIST_NAVIGATION_EPG_BY_CHANNEL: { + // startActivity(EventEpgListActivity.class); + // return true; + // } + // + // case LIST_NAVIGATION_RECORDINGS: { + // startActivity(RecordingListActivity.class); + // return true; + // } + // + // case LIST_NAVIGATION_TIMERS: { + // startActivity(TimerListActivity.class); + // return true; + // } + // + // } + // return false; + // } + // }); + // getSupportActionBar().setSelectedNavigationItem( + // getListNavigationIndex()); + + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Preferences.setLocale(this); + setContentView(getMainLayout()); + progress = new ProgressDialog(this); + progress.setCancelable(false); + progress.setCanceledOnTouchOutside(false); + // progress.setOnCancelListener(new OnCancelListener() { + // @Override + // public void onCancel(DialogInterface dialog) { + // + // } + // }); + + initActionBar(); + + initListNavigation(); + + initLeftDrawer(savedInstanceState); + + // new OnNavigationListener() { + // @Override + // public boolean onNavigationItemSelected(int itemPosition, long + // itemId) { + // System.err.println("itemPosition: "+ itemPosition +", itemId:" + + // itemId); + // rturn false; + // } + // }); + + // your logic for click listner + // setListenerForActionBarCustomView(actionBarView); + + // private void setListenerForActionBarCustomView(View actionBarView) { + // ActionBarCustomViewOnClickListener actionBarCustomViewOnClickListener + // = new ActionBarCustomViewOnClickListener(); + // actionBarView.findViewById(R.id.text_view_title).setOnClickListener(actionBarCustomViewOnClickListener); + // } + + } + + protected void initLeftDrawer(final Bundle savedInstanceState) { + + mDrawerTitle = getTitle(); + + mTitles = getResources().getStringArray(R.array.navigation_array); + + mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); + mDrawerList = (ListView) findViewById(R.id.left_drawer); + + // set a custom shadow that overlays the main content when the drawer + // opens + mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, + GravityCompat.START); + // set up the drawer's list view with items and click listener + mDrawerList.setAdapter(new ArrayAdapter<String>(this, + R.layout.drawer_list_item, mTitles)); + mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); + + // enable ActionBar app icon to behave as action to toggle nav drawer + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + // ActionBarDrawerToggle ties together the the proper interactions + // between the sliding drawer and the action bar app icon + mDrawerToggle = new ActionBarDrawerToggle(this, /* host Activity */ + mDrawerLayout, /* DrawerLayout object */ + R.drawable.ic_drawer, /* nav drawer image to replace 'Up' caret */ + R.string.drawer_open, /* "open drawer" description for accessibility */ + R.string.drawer_close /* "close drawer" description for accessibility */ + ) { + public void onDrawerClosed(View view) { + // getSupportActionBar().setTitle(mTitle); + //invalidateOptionsMenu(); // creates call to + // onPrepareOptionsMenu() + } + + public void onDrawerOpened(View drawerView) { + // getSupportActionBar().setTitle(mDrawerTitle); + //invalidateOptionsMenu(); // creates call to + // onPrepareOptionsMenu() + } + }; + mDrawerLayout.setDrawerListener(mDrawerToggle); + +// if (savedInstanceState == null) { +// selectItem(0); +// } + + } + + /* The click listner for ListView in the navigation drawer */ + private class DrawerItemClickListener implements + ListView.OnItemClickListener { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, + long id) { + selectItem(position); + } + } + + private void selectItem(int position) { + + // update selected item and title, then close the drawer + mDrawerList.setItemChecked(position, true); + // setTitle(mPlanetTitles[position]); + + switch (position) { + + case LIST_NAVIGATION_CHANNELS: { + startActivity(ChannelListActivity.class); + break; + } + case LIST_NAVIGATION_EPG_BY_TIME: { + startActivity(TimeEpgListActivity.class); + break; + } + + case LIST_NAVIGATION_EPG_BY_CHANNEL: { + startActivity(EventEpgListActivity.class); + break; + } + + case LIST_NAVIGATION_RECORDINGS: { + startActivity(RecordingListActivity.class); + break; + } + + case LIST_NAVIGATION_TIMERS: { + startActivity(TimerListActivity.class); + break; + } + case LIST_NAVIGATION_REMOTE: { + startActivity(RemoteActivity.class); + break; + } + } + + mDrawerLayout.closeDrawer(mDrawerList); + } + + protected void initActionBar() { + getSupportActionBar().setHomeButtonEnabled(true); + } + + public void startActivity(final Class<?> clazz) { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setClass(this, clazz); + startActivity(intent); + finish(); + } + + protected int getBaseMenu() { + return R.menu.refresh_filter_menu; + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + + // MenuItem item; + // item = menu.add(MENU_GROUP_REFRESH, MENU_REFRESH, 0, + // R.string.refresh); + // item.setIcon(R.drawable.ic_menu_refresh); + // item.setAlphabeticShortcut('r'); + final MenuInflater inf = getMenuInflater(); + inf.inflate(getBaseMenu(), menu); + + // SearchView searchView = (SearchView) + // menu.findItem(R.id.menu_search).getActionView(); + // searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); + + return true; + } + + abstract protected void refresh(); + + abstract protected void retry(); + + // abstract protected SvdrpClient<Result> getClient(); + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + + if (mDrawerToggle != null && mDrawerToggle.onOptionsItemSelected(item)) { + return true; + } + + switch (item.getItemId()) { + case R.id.list_refresh: + backupViewSelection(); + refresh(); + return true; + case R.id.list_filter: { + onSearchRequested(); + return true; + } + case android.R.id.home: + final Intent intent = new Intent(this, VdrManagerActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + default: + return false; + } + } + + protected void setCurrent(final Channel channel) { + getApp().setCurrentChannel(channel); + } + + protected VdrManagerApp getApp() { + final VdrManagerApp app = (VdrManagerApp) getApplication(); + return app; + } + + // protected Channel getCurrentChannel(){ + // final Channel channel = ((VdrManagerApp) getApplication()) + // .getCurrentChannel(); + // return channel; + // } + + protected void say(final int res) { + say(this.getString(res)); + } + + protected void say(final String msg) { + Utils.say(this, msg); + } + + protected boolean noConnection(final SvdrpEvent event) { + switch (event) { + case CONNECTION_TIMEOUT: + say(R.string.progress_connect_timeout); + switchNoConnection(); + case CONNECT_ERROR: + say(R.string.progress_connect_error); + switchNoConnection(); + break; + case FINISHED_ABNORMALY: + alert(R.string.progress_connect_finished_abnormal); + switchNoConnection(); + break; + case LOGIN_ERROR: + say(R.string.progress_login_error); + switchNoConnection(); + break; + default: + return false; + } + return true; + } + + protected void alert(final String msg) { + if (isFinishing()) { + return; + } + new AlertDialog.Builder(this)// + .setMessage(msg)// + .setPositiveButton(android.R.string.ok, null)// + .create()// + .show();// + } + + protected void alert(final int resId) { + alert(getString(resId)); + } + + protected void restoreViewSelection() { + listView.setSelectionFromTop(index, top); + } + + protected void backupViewSelection() { + index = listView.getFirstVisiblePosition(); + final View v = listView.getChildAt(0); + top = (v == null) ? 0 : v.getTop(); + } + + int index; + int top; + + protected boolean checkInternetConnection() { + if (Utils.checkInternetConnection(this)) { + return true; + } + noInternetConnection(); + return false; + } + + // public void svdrpEvent(Result result) { + // resultReceived(result); + // } + + @Override + public void svdrpEvent(final SvdrpEvent event, final Throwable t) { + progress.dismiss(); + Utils.say(this, t.getMessage()); + } + + protected void addListener( + final SvdrpAsyncTask<Result, SvdrpClient<Result>> task) { + task.addSvdrpExceptionListener(this); + task.addSvdrpListener(this); + task.addSvdrpFinishedListener(this); + } + + @Override + public void svdrpEvent(final SvdrpEvent event) { + + switch (event) { + case LOGIN: + break; + case COMMAND_SENDING: + break; + case CONNECTING: + progress.setProgressStyle(ProgressDialog.STYLE_SPINNER); + setMessage(R.string.progress_connect); + if (!isFinishing()) { + progress.show(); + } + break; + case LOGGED_IN: + setMessage(R.string.progress_login); + break; + case COMMAND_SENT: + setMessage(getProgressTextId()); + break; + case DISCONNECTING: + setMessage(R.string.progress_disconnect); + break; + case DISCONNECTED: + break; + case ABORTED: + progress.dismiss(); + say(R.string.aborted); + break; + case ERROR: + progress.dismiss(); + alert(R.string.epg_client_errors); + break; + case CONNECTED: + connected(); + break; + case CONNECTION_TIMEOUT: + case CONNECT_ERROR: + case FINISHED_ABNORMALY: + case LOGIN_ERROR: + progress.dismiss(); + noConnection(event); + break; + case CACHE_HIT: + progress.dismiss(); + cacheHit(); + return; + case FINISHED_SUCCESS: + progress.dismiss(); + break; + } + // case RESULT_RECEIVED: + // resultReceived(result); + // break; + // } + } + + protected int getProgressTextId() { + return R.string.progress_loading; + } + + private void setMessage(final int progressConnect) { + progress.setMessage(getString(progressConnect)); + } + + protected boolean finishedSuccess = false; + + protected void cacheHit() { + + } + + /** + * @return false, if no results found + */ + protected abstract boolean finishedSuccess(List<Result> results); + + // /** + // * @param result + // */ + // protected abstract void resultReceived(Result result); + + protected void connected() { + if (flipper != null) { + flipper.setDisplayedChild(0); + } + } + + public void svdrpException(final SvdrpException exception) { + // svdrpException(exception); + // Log.w(TAG, exception); + alert(getString(R.string.vdr_error_text, exception.getMessage())); + } + + @Override + protected void onDestroy() { + if (progress.isShowing()) { + progress.dismiss(); + } + super.onDestroy(); + } + + @Override + public void finished(final List<Result> results) { + if (finishedSuccess(results)) { + finishedSuccess = true; + restoreViewSelection(); + } else { + say(R.string.epg_no_items); + } + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseEventAdapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseEventAdapter.java new file mode 100644 index 0000000..17edd42 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseEventAdapter.java @@ -0,0 +1,365 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.text.TextUtils; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventFormatter; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.data.Recording; +import de.bjusystems.vdrmanager.data.TimerMatch; +import de.bjusystems.vdrmanager.data.Timerable; + +abstract class BaseEventAdapter<T extends EventListItem> extends + ArrayAdapter<T> implements Filterable +// , SectionIndexer +{ + + protected final static int TYPE_ITEM = 0; + protected final static int TYPE_HEADER = 1; + protected final int layout; + protected final LayoutInflater inflater; + protected final List<T> items = new ArrayList<T>(); + + protected boolean hideDescription = true; + + protected boolean hideChannelName = false; + + String highlight; + + /** + * Lock used to modify the content of {@link #mObjects}. Any write operation + * performed on the array should be synchronized on this lock. This lock is + * also used by the filter (see {@link #getFilter()} to make a synchronized + * copy of the original array of data. + */ + private final Object _Lock = new Object(); + + public BaseEventAdapter(final Context context, int layout) { + super(context, layout); + this.layout = layout; + inflater = LayoutInflater.from(context); + } + + @Override + public int getViewTypeCount() { + return 2; + } + + @Override + public void add(T object) { + items.add(object); + // if (object.isHeader()) { + // sections.add(object.getHeader()); + // } + super.add(object); + } + + @Override + public int getItemViewType(int position) { + + // get item + final EventListItem item = getItem(position); + + if (item.isHeader()) { + return TYPE_HEADER; + } + return TYPE_ITEM; + } + + public static class EventListItemHeaderHolder { + TextView header; + } + + private boolean canReuseConvertView(View convertView, int itemViewType) { + if (convertView == null) { + return false; + } + Object o = convertView.getTag(); + if (itemViewType == TYPE_ITEM) { + return o instanceof EventListItemHolder; + } + + if (itemViewType == TYPE_HEADER) { + return o instanceof EventListItemHeaderHolder; + } + + return false; + + } + + @Override + public View getView(final int position, View convertView, + final ViewGroup parent) { + + // get item + final EventListItem item = getItem(position); + + Object holder = null; + int type = getItemViewType(position); + if (canReuseConvertView(convertView, type) == false) { + switch (type) { + case TYPE_ITEM: + convertView = inflater.inflate(layout, null); + holder = getEventViewHolder(item, convertView); + break; + case TYPE_HEADER: + convertView = inflater.inflate(R.layout.header_item, null); + holder = getHeaderViewHolder(item, convertView); + break; + } + convertView.setTag(holder); + } else { + holder = convertView.getTag(); + } + + if (type == TYPE_ITEM) { + fillEventViewHolder((EventListItemHolder) holder, item); + } else { + ((EventListItemHeaderHolder) holder).header.setText(item + .getHeader()); + } + return convertView; + } + + protected EventListItemHolder getEventViewHolder(EventListItem item, + View view) { + + EventListItemHolder itemHolder = new EventListItemHolder(); + + itemHolder = new EventListItemHolder(); + + itemHolder.state = (ImageView) view.findViewById(R.id.timer_item_state); + itemHolder.other = (ImageView) view.findViewById(R.id.timer_item_other); + itemHolder.time = (TextView) view.findViewById(R.id.timer_item_time); + itemHolder.channel = (TextView) view + .findViewById(R.id.timer_item_channel); + itemHolder.title = (TextView) view.findViewById(R.id.timer_item_title); + itemHolder.progress = (ProgressBar) view + .findViewById(R.id.timer_progress); + itemHolder.shortText = (TextView) view + .findViewById(R.id.timer_item_shorttext); + itemHolder.duration = (TextView) view + .findViewById(R.id.timer_item_duration); + itemHolder.description = (TextView) view + .findViewById(R.id.event_item_description); + return itemHolder; + } + + protected void handleState(EventListItemHolder itemHolder, + EventListItem item) { + if (item.getEvent() instanceof Timerable) { + TimerMatch match = ((Timerable) item.getEvent()).getTimerMatch(); + switch (((Timerable) item.getEvent()).getTimerState()) { + case Active: + itemHolder.state.setImageResource(Utils.getTimerStateDrawable( + match, R.drawable.timer_active, + R.drawable.timer_active_begin, + R.drawable.timer_active_end, + R.drawable.timer_active_conflict)); + break; + case Inactive: + itemHolder.state.setImageResource(Utils.getTimerStateDrawable( + match, R.drawable.timer_inactive, + R.drawable.timer_inactive_begin, + R.drawable.timer_inactive_end, + R.drawable.timer_inactive)); + break; + case Recording: + itemHolder.state.setImageResource(Utils.getTimerStateDrawable( + match, R.drawable.timer_recording, + R.drawable.timer_recording_begin, + R.drawable.timer_recording_end, + R.drawable.timer_recording_conflict)); + break; + case None: + itemHolder.state.setImageResource(R.drawable.timer_none); + break; + } + } else { + itemHolder.state.setImageResource(R.drawable.timer_none); + } + + } + + public void fillEventViewHolder(EventListItemHolder itemHolder, + EventListItem item) { + + itemHolder.state.setVisibility(View.VISIBLE); + + handleState(itemHolder, item); + + final EventFormatter formatter = getEventFormatter(item); + itemHolder.time.setText(formatter.getTime()); + if (hideChannelName) { + itemHolder.channel.setVisibility(View.GONE); + } else { + itemHolder.channel.setText(item.getChannelName()); + } + + CharSequence title = Utils.highlight(formatter.getTitle(), highlight); + CharSequence shortText = Utils.highlight(formatter.getShortText(), + highlight); + itemHolder.title.setText(title); + itemHolder.shortText.setText(shortText); + + if (TextUtils.isEmpty(formatter.getDescription()) == false + && hideDescription == false) { + Pair<Boolean, CharSequence> desc = Utils.highlight2( + formatter.getDescription(), highlight); + itemHolder.description.setVisibility(View.VISIBLE); + itemHolder.description.setText(desc.second); + } else { + itemHolder.description.setVisibility(View.GONE); + } + + // TODO better render of duration + int p = Utils.getProgress(item.getEvent()); + if (p == -1) { + itemHolder.progress.setVisibility(View.GONE); + int dura = Utils.getDuration(item); + itemHolder.duration.setText(getContext().getString( + R.string.epg_duration_template, dura)); + } else { + itemHolder.progress.setVisibility(View.VISIBLE); + itemHolder.progress.setProgress(p); + int dura = Utils.getDuration(item.getEvent()); + int rest = dura - (dura * p / 100); + // on live recordings the amount of already recorded time + if (item.getEvent() instanceof Recording) { + rest = dura - rest; + } + itemHolder.duration.setText(getContext().getString( + R.string.epg_duration_template_live, rest, dura)); + } + } + + protected EventListItemHeaderHolder getHeaderViewHolder(EventListItem item, + View view) { + EventListItemHeaderHolder itemHolder = new EventListItemHeaderHolder(); + itemHolder.header = (TextView) view.findViewById(R.id.header_item); + return itemHolder; + } + + protected EventFormatter getEventFormatter(Event event) { + return new EventFormatter(event); + } + + protected void addSuper(T item) { + super.add(item); + } + + protected void clearSuper() { + super.clear(); + } + + public boolean isHideDescription() { + return hideDescription; + } + + public void setHideDescription(boolean hideDescription) { + this.hideDescription = hideDescription; + } + + public boolean isHideChannelName() { + return hideChannelName; + } + + public void setHideChannelName(boolean hideChannelName) { + this.hideChannelName = hideChannelName; + } + + protected boolean isHeader(EventListItem item) { + return item.isHeader(); + } + + // TODO implement locking in performFiltering, check the parent class + // http://stackoverflow.com/questions/5846385/how-to-update-android-listview-with-dynamic-data-in-real-time + public Filter getFilter() { + return new Filter() { + /** + * + */ + EventListItem prevHead = null; + + @Override + protected FilterResults performFiltering(CharSequence arg0) { + highlight = arg0.toString().toLowerCase(); + ArrayList<EventListItem> result = new ArrayList<EventListItem>(); + for (EventListItem event : items) { + if (isHeader(event)) { + prevHead = event; + // result.add(event); + continue; + } + if (event.getTitle().toLowerCase() + .indexOf(String.valueOf(arg0).toLowerCase()) != -1 + || event.getShortText() + .toLowerCase() + .indexOf(String.valueOf(arg0).toLowerCase()) != -1) { + if (prevHead != null) { + result.add(prevHead); + prevHead = null; + } + result.add(event); + } + } + + FilterResults fr = new FilterResults(); + fr.count = result.size(); + fr.values = result; + return fr; + } + + @Override + protected void publishResults(CharSequence arg0, FilterResults arg1) { + clearSuper(); + for (T item : (ArrayList<T>) arg1.values) { + addSuper(item); + } + notifyDataSetChanged(); + } + }; + } + + // @Override + // public int getPositionForSection(int section) { + // return 0; + // } + + // @Override + // public int getSectionForPosition(int position) { + // TODO Auto-generated method stub + // return 0; + // } + + // ArrayList<String> sections = new ArrayList<String>(); + + // @Override + // public Object[] getSections() { + // try { + // return sections.toArray(); + // } finally { + // sections.clear(); + // } + // } + + @Override + public void clear() { + super.clear(); + items.clear(); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseEventListActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseEventListActivity.java new file mode 100644 index 0000000..f1779d8 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseEventListActivity.java @@ -0,0 +1,576 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.Intents; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.data.P; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.gui.SimpleGestureFilter.SimpleGestureListener; +import de.bjusystems.vdrmanager.tasks.VoidAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpException; + +/** + * @author lado + * + */ +public abstract class BaseEventListActivity<T extends Event> extends + BaseActivity<T, ListView> implements OnItemClickListener, + SimpleGestureListener { + + public static final String TAG = BaseEventListActivity.class.getName(); + + public static final int MENU_GROUP_SHARE = 90; + + public static final int MENU_SHARE = 90; + + public static final int MENU_GROUP_TO_CAL = 91; + + public static final int MENU_TO_CAL = 91; + + private SimpleGestureFilter detector; + + protected BaseEventAdapter<EventListItem> adapter; + + protected String highlight = null; + + protected Date lastUpdate = null; + + protected static final Date FUTURE = new Date(Long.MAX_VALUE); + + // private static final Date BEGIN = new Date(0); + + protected Channel currentChannel = null; + + // protected List<T> results = new ArrayList<T>(); + + AlertDialog sortByDialog = null; + + public static final int MENU_GROUP_DEFAULT = 0; + + public static final int MENU_GROUP_ALPHABET = 1; + + public static final int MENU_GROUP_CHANNEL = 2; + + protected int sortBy; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + sortBy = Preferences.get(this, getViewID() + "_" + P.EPG_LAST_SORT, + MENU_GROUP_DEFAULT); + // Attach view + setTitle(getWindowTitle()); + initFlipper(); + detector = new SimpleGestureFilter(this, this); + + initChannel(); + } + + private void initChannel() { + currentChannel = getApp().getCurrentChannel(); + // currentChannel = getIntent() + // .getParcelableExtra(Intents.CURRENT_CHANNEL); + } + + @Override + protected void onResume() { + super.onResume(); + if (notifyDataSetChangedOnResume()) { + adapter.notifyDataSetChanged(); + } + } + + /* + * (non-Javadoc) + * + * @see + * de.bjusystems.vdrmanager.gui.BaseActivity#onCreateOptionsMenu(android + * .view.Menu) + */ + @Override + public boolean onCreateOptionsMenu( + final Menu menu) { + super.onCreateOptionsMenu(menu); + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.epg_list_menu, menu); + return true; + } + + /** + * Prepare the current event and the chained events for + * + * @param event + */ + protected int prepareDetailsViewData(final EventListItem item, int pos) { + final VdrManagerApp app = (VdrManagerApp) getApplication(); + // remember event for details view and timer things + app.setCurrentEvent(item.getEvent()); + ArrayList<Event> current = new ArrayList<Event>(); + for (int i = 0; i < adapter.getCount(); ++i) { + EventListItem item2 = adapter.getItem(i); + if(item2.isHeader()){ + continue; + } + current.add(item2.getEvent()); + } + app.setCurrentEpgList(current); + for (int i = 0; i < pos; ++i) { + if (current.get(i) == item.getEvent()) { + return i; + } + } + + return 0; + } + + /* + * (non-Javadoc) + * + * @see android.app.Activity#onContextItemSelected(android.view.MenuItem) + */ + @Override + public boolean onContextItemSelected(final MenuItem item) { + + final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item + .getMenuInfo(); + final EventListItem event = adapter.getItem(info.position); + + final int itemId = item.getItemId(); + + switch (itemId) { + + case R.id.epg_item_menu_live_tv: { + Utils.stream(this, event); + break; + } + + case MENU_SHARE: { + Utils.shareEvent(this, event); + break; + } + + case MENU_TO_CAL: { + Utils.addCalendarEvent(this, event); + break; + } + + case R.id.epg_item_menu_switchto: { + Utils.switchTo(this, event.getChannelId(), event.getChannelName()); + break; + } + + default: + return super.onContextItemSelected(item); + } + + return true; + } + + protected int getAvailableSortByEntries() { + return 0; + } + + protected void fillAdapter() { + + } + + /* + * (non-Javadoc) + * + * @see + * de.bjusystems.vdrmanager.gui.BaseActivity#onOptionsItemSelected(android + * .view.MenuItem) + */ + public boolean onOptionsItemSelected( + final MenuItem item) { + + switch (item.getItemId()) { + + case R.id.epg_list_sort_menu: { + + if (sortByDialog == null) { + sortByDialog = new AlertDialog.Builder(this) + .setTitle(R.string.sort) + .setIcon(android.R.drawable.ic_menu_sort_alphabetically) + .setSingleChoiceItems(getAvailableSortByEntries(), + sortBy, new DialogInterface.OnClickListener() { + @Override + public void onClick( + final DialogInterface dialog, + final int which) { + + if (sortBy == which) { + sortByDialog.dismiss(); + return; + } + + sortBy = which; + + new VoidAsyncTask() { + + @Override + protected Void doInBackground( + final Void... params) { + Preferences + .set(BaseEventListActivity.this, + getViewID() + + "_" + + P.EPG_LAST_SORT, + sortBy); + return null; + } + }.execute(); + + sortByDialog.dismiss(); + fillAdapter(); + } + + }).create(); + } + + sortByDialog.show(); + + return true; + } + + // switch (item.getItemId()) { + // case R.id.epg_menu_search: + // startSearchManager(); + // super.onSearchRequested(); + // break; + // case R.id.epg_menu_times: + // intent = new Intent(); + // /intent.setClass(this, EpgSearchTimesListActivity.class); + // startActivity(intent); + // break; + } + return super.onOptionsItemSelected(item); + } + + /* + * (non-Javadoc) + * + * @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, + * android.view.View, android.view.ContextMenu.ContextMenuInfo) + */ + @Override + public void onCreateContextMenu(final ContextMenu menu, final View v, + final ContextMenuInfo menuInfo) { + + // if (v.getId() == R.id.whatson_list) { + final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + + // set menu title + final EventListItem item = adapter.getItem(info.position); + + if (item.isHeader()) { + return; + } + + final MenuItem mi = menu.findItem(R.id.epg_item_menu_live_tv); + if (item.isLive() && item.getStreamId() != null) { + + mi.setVisible(true); + + } else { + + mi.setVisible(false); + } + menu.add(MENU_GROUP_SHARE, MENU_SHARE, 0, R.string.share); + menu.add(MENU_GROUP_TO_CAL, MENU_TO_CAL, 0, R.string.addtocal); + super.onCreateContextMenu(menu, v, menuInfo); + + } + + /** + * @param parent + * @param view + * @param position + * @param id + */ + @Override + public void onItemClick(final AdapterView<?> parent, final View view, + final int position, final long id) { + + // find and remember item + final EventListItem item = adapter.getItem(position); + + if (item.isHeader()) { + return; + } + + int current = prepareDetailsViewData(item, position); + + // show details + final Intent intent = new Intent(this, EpgDetailsActivity.class); + // | Intent.FLAG_ACTIVITY_SINGLE_TOP); + if (highlight != null) { + intent.putExtra(Intents.HIGHLIGHT, highlight); + } + intent.putExtra(Intents.CURRENT_EPG, current); + startActivityForResult(intent, + TimerDetailsActivity.REQUEST_CODE_TIMER_MODIFIED); + } + + protected boolean notifyDataSetChangedOnResume() { + return true; + } + + @Override + protected void onPause() { + super.onPause(); + // if (epgClient != null) { + // epgClient.abort(); + // } + // if (progress != null) { + // progress.dismiss(); + // progress = null; + // } + } + + // protected void resultReceived(T result) { + // results.add(result); + // } + + @Override + protected void onRestoreInstanceState(final Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + final int index = savedInstanceState.getInt("INDEX"); + final int top = savedInstanceState.getInt("TOP"); + listView.setSelectionFromTop(index, top); + } + + @Override + protected void onSaveInstanceState(final Bundle outState) { + final int index = listView.getFirstVisiblePosition(); + final View v = listView.getChildAt(0); + final int top = (v == null) ? 0 : v.getTop(); + outState.putInt("INDEX", index); + outState.putInt("TOP", top); + super.onSaveInstanceState(outState); + } + + protected void dismiss(final AlertDialog dialog) { + if (dialog == null) { + return; + } + dialog.dismiss(); + } + + public boolean onSearchRequested() { + final InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + inputMgr.toggleSoftInput(0, 0); + return true; + } + + protected void startSearchManager() { + final Bundle appData = new Bundle(); + startSearch(highlight, false, appData, false); + } + + @Override + public boolean dispatchTouchEvent(final MotionEvent me) { + this.detector.onTouchEvent(me); + return super.dispatchTouchEvent(me); + } + + @Override + public void onSwipe(final int direction) { + + } + + @Override + public void onDoubleTap() { + + } + + protected void sortItemsByChannel(final List<T> result) { + final Comparator<T> comparator = new Comparator<T>() { + + @Override + public int compare(final T item1, final T item2) { + return item1.getChannelNumber().compareTo( + item2.getChannelNumber()); + } + }; + Collections.sort(result, comparator); + } + + protected void sortItemsByTime(final List<T> result) { + sortItemsByTime(result, false); + } + + protected void sortItemsByTime(final List<T> result, final boolean reverse) { + Collections.sort(result, new TimeAndChannelComparator(reverse)); + } + + @Override + public void svdrpException(final SvdrpException exception) { + Log.w(TAG, exception); + alert(getString(R.string.vdr_error_text, exception.getMessage())); + } + + abstract protected boolean finishedSuccessImpl(List<T> results); + + protected String getViewID() { + return this.getClass().getSimpleName(); + } + + protected void pushResultCountToTitle() { + setTitle(getString(R.string.epg_window_title_count, getWindowTitle(), + getCACHE().size())); + } + + @Override + synchronized protected final boolean finishedSuccess(final List<T> results) { + // ProgressDialog dialog = new ProgressDialog(this); + // dialog.setMessage("Loading"); + // dialog.show(); + try { + lastUpdate = new Date(); + final boolean r = finishedSuccessImpl(results); + if (r == false) { + adapter.clear(); + adapter.notifyDataSetChanged(); + } + return r; + } finally { + // dialog.dismiss(); + // results.clear(); + } + } + + @Override + protected boolean displayingResults() { + return getCACHE().isEmpty() == false; + } + + class TitleComparator implements Comparator<Event> { + + @Override + public int compare(final Event lhs, final Event rhs) { + if (lhs == null || lhs.getTitle() == null) { + return 1; + } + if (rhs == null || rhs.getTitle() == null) { + return 0; + } + return lhs.getTitle().compareToIgnoreCase(rhs.getTitle()); + } + }; + + class TimeAndChannelComparator implements Comparator<Event> { + boolean r = false; + + TimeAndChannelComparator() { + this(false); + } + + TimeAndChannelComparator(final boolean r) { + this.r = r; + } + + @Override + public int compare(final Event item1, final Event item2) { + + final int c = item1.getStart().compareTo(item2.getStart()); + if (c != 0) { + if (r == false) { + return c; + } + return -1 * c; + } + if (item1.getChannelNumber() == null + && item2.getChannelNumber() == null) { + return 0; + } + if (item1.getChannelNumber() == null) { + return 1; + } + if (item2.getChannelNumber() == null) { + return -1; + } + return item1.getChannelNumber().compareTo(item2.getChannelNumber()); + } + } + + class TimeComparator implements Comparator<Event> { + boolean r = false; + + TimeComparator(final boolean r) { + this.r = r; + } + + @Override + public int compare(final Event item1, final Event item2) { + + final int c = item1.getStart().compareTo(item2.getStart()); + if (c == 0) { + return c; + } + if (r == false) { + return c; + } + return -1 * c; + } + } + + class ChannelComparator implements Comparator<Event> { + + @Override + public int compare(final Event item1, final Event item2) { + + if (item1.getChannelNumber() == null + && item2.getChannelNumber() == null) { + return 0; + } + if (item1.getChannelNumber() == null) { + return 1; + } + if (item2.getChannelNumber() == null) { + return -1; + } + return item1.getChannelNumber().compareTo(item2.getChannelNumber()); + } + } + + public void clearCache() { + getCACHE().clear(); + } + + protected abstract List<T> getCACHE(); + + // @Override + // protected void connected() { + // super.connected(); + // results.clear(); + // } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BasePreferencesActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BasePreferencesActivity.java new file mode 100644 index 0000000..50e9bb7 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BasePreferencesActivity.java @@ -0,0 +1,203 @@ +package de.bjusystems.vdrmanager.gui; + +import android.preference.EditTextPreference; +import android.preference.ListPreference; +import android.preference.Preference; +import android.preference.PreferenceActivity; +import android.text.TextUtils; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.FetchEditTextPreference; + +/** + * + * Basis class for PreferencesActivities with some goodies in it + * @author lado + * + */ +public abstract class BasePreferencesActivity extends PreferenceActivity { + + protected void updateSummary(Preference ep) { + if (ep instanceof EditTextPreference) { + updateSummary((EditTextPreference) ep); + } else if (ep instanceof ListPreference) { + updateSummary((ListPreference) ep); + } else if(ep instanceof FetchEditTextPreference){ + updateSummary((FetchEditTextPreference)ep); + } + } + + /** + * If text set add it to the summary + * + * @param ep + */ + protected void updateSummary(FetchEditTextPreference ep) { + String text = ep.getText(); + if (text == null) { + text = ""; + } + setSummary(text, ep); + } + + /** + * If text set add it to the summary + * + * @param ep + */ + protected void updateSummary(EditTextPreference ep) { + String text = ep.getText(); + if (text == null) { + return; + } + + if(isPassword(ep.getEditText())){ + text = text.replaceAll(".", "*"); + } + + setSummary(text, ep); + } + + protected boolean isPassword(EditText et){ + if((et.getInputType() & EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) == EditorInfo.TYPE_TEXT_VARIATION_PASSWORD){ + return true; + } + return false; + } + + protected void setSummary(CharSequence text, Preference ep){ + CharSequence sm = ep.getSummary(); + String sum; + if (sm != null) { + sum = ep.getSummary().toString(); + sum = substringBeforeLast(sum, + getString(R.string.prefs_current_value)).trim(); + } else { + sum = ""; + } + + if(TextUtils.isEmpty(text)){ + text = getString(R.string.prefs_current_value_not_set); + } + + if (isBlank(sum)) { + sum = getString(R.string.prefs_current_value_template, text); + } else { + sum = sum + " " + + getString(R.string.prefs_current_value_template, text); + } + ep.setSummary(sum); + } + + protected void updateSummary(ListPreference ep) { + CharSequence text = ep.getEntry(); + + if (text == null) { + return; + } + setSummary(text, ep); + } + + /** + * <p> + * Gets the substring before the last occurrence of a separator. The + * separator is not returned. + * </p> + * + * <p> + * A <code>null</code> string input will return <code>null</code>. An empty + * ("") string input will return the empty string. An empty or + * <code>null</code> separator will return the input string. + * </p> + * + * <pre> + * StringUtils.substringBeforeLast(null, *) = null + * StringUtils.substringBeforeLast("", *) = "" + * StringUtils.substringBeforeLast("abcba", "b") = "abc" + * StringUtils.substringBeforeLast("abc", "c") = "ab" + * StringUtils.substringBeforeLast("a", "a") = "" + * StringUtils.substringBeforeLast("a", "z") = "a" + * StringUtils.substringBeforeLast("a", null) = "a" + * StringUtils.substringBeforeLast("a", "") = "a" + * </pre> + * + * @param str + * the String to get a substring from, may be null + * @param separator + * the String to search for, may be null + * @return the substring before the last occurrence of the separator, + * <code>null</code> if null String input + * @since 2.0 + */ + public static String substringBeforeLast(String str, String separator) { + if (isEmpty(str) || isEmpty(separator)) { + return str; + } + int pos = str.lastIndexOf(separator); + if (pos == -1) { + return str; + } + return str.substring(0, pos); + } + + // Empty checks + // ----------------------------------------------------------------------- + /** + * <p> + * Checks if a String is empty ("") or null. + * </p> + * + * <pre> + * StringUtils.isEmpty(null) = true + * StringUtils.isEmpty("") = true + * StringUtils.isEmpty(" ") = false + * StringUtils.isEmpty("bob") = false + * StringUtils.isEmpty(" bob ") = false + * </pre> + * + * <p> + * NOTE: This method changed in Lang version 2.0. It no longer trims the + * String. That functionality is available in isBlank(). + * </p> + * + * @param str + * the String to check, may be null + * @return <code>true</code> if the String is empty or null + */ + public static boolean isEmpty(String str) { + return str == null || str.length() == 0; + } + + /** + * <p> + * Checks if a String is whitespace, empty ("") or null. + * </p> + * + * <pre> + * StringUtils.isBlank(null) = true + * StringUtils.isBlank("") = true + * StringUtils.isBlank(" ") = true + * StringUtils.isBlank("bob") = false + * StringUtils.isBlank(" bob ") = false + * </pre> + * + * @param str + * the String to check, may be null + * @return <code>true</code> if the String is null, empty or whitespace + * @since 2.0 + */ + public static boolean isBlank(String str) { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return true; + } + for (int i = 0; i < strLen; i++) { + if ((Character.isWhitespace(str.charAt(i)) == false)) { + return false; + } + } + return true; + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseTimerEditActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseTimerEditActivity.java new file mode 100644 index 0000000..b377144 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/BaseTimerEditActivity.java @@ -0,0 +1,244 @@ +package de.bjusystems.vdrmanager.gui; + +import android.content.Intent; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.Intents; +import de.bjusystems.vdrmanager.data.EpgCache; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.data.TimerMatch; +import de.bjusystems.vdrmanager.data.Timerable; +import de.bjusystems.vdrmanager.tasks.DeleteTimerTask; +import de.bjusystems.vdrmanager.tasks.ToggleTimerTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpEvent; + +/** + * + * This class is a base class for all the listings, which can deal with timers + * + * @author lado + * + * @param <T> + * Class extending Event + */ +public abstract class BaseTimerEditActivity<T extends Event> extends + BaseEventListActivity<T> implements OnClickListener // SvdrpAsyncListener<Timer>, +{ + + // private static final ScheduledExecutorService worker = Executors + // .newSingleThreadScheduledExecutor(); + + // /@Override + // public boolean onPrepareOptionsMenu(Menu menu) { + // return super.onPrepareOptionsMenu(menu); + // } + /* + * (non-Javadoc) + * + * @see android.app.Activity#onContextItemSelected(android.view.MenuItem) + */ + // @Override + public boolean onContextItemSelected(final MenuItem item) { + // + final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item + .getMenuInfo(); + final EventListItem event = adapter.getItem(info.position); + getApp().setCurrentEvent(event.getEvent()); + switch (item.getItemId()) { + case R.id.epg_item_menu_timer_add: { + + getApp().setCurrentTimer(createTimer(event)); + final Intent intent = new Intent(); + intent.setClass(this, TimerDetailsActivity.class); + intent.putExtra(Intents.TIMER_OP, Intents.ADD_TIMER); + startActivityForResult(intent, + TimerDetailsActivity.REQUEST_CODE_TIMER_ADD); + } + break; + case R.id.epg_item_menu_timer_modify: { + getApp().setCurrentTimer(getTimer(event)); + final Intent intent = new Intent(); + intent.setClass(this, TimerDetailsActivity.class); + intent.putExtra(Intents.TIMER_OP, Intents.EDIT_TIMER); + startActivityForResult(intent, + TimerDetailsActivity.REQUEST_CODE_TIMER_EDIT); + break; + } + case R.id.epg_item_menu_timer_delete: { + backupViewSelection(); + deleteTimer(getTimer(event)); + break; + } + case R.id.epg_item_menu_timer_toggle: { + backupViewSelection(); + toggleTimer(getTimer(event)); + break; + } + default: + return super.onContextItemSelected(item); + } + + return true; + } + + /* + * (non-Javadoc) + * + * @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, + * android.view.View, android.view.ContextMenu.ContextMenuInfo) + */ + @Override + public void onCreateContextMenu(final ContextMenu menu, final View v, + final ContextMenuInfo menuInfo) { + // if (v.getId() == R.id.whatson_list) { + final MenuInflater inflater = getMenuInflater(); + final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + + // set menu title + final EventListItem item = adapter.getItem(info.position); + + if (item.isHeader()) { + return; + } + + // final EventFormatter formatter = new EventFormatter(item); + menu.setHeaderTitle(item.getTitle()); + + inflater.inflate(R.menu.epg_list_item_menu, menu); + Timer timer = getTimer(item); + // remove unneeded menu items + if (timer == null || (Utils.getTimerMatch(item, timer) == TimerMatch.Full == false)) { //Bug #1372 + super.onCreateContextMenu(menu, v, menuInfo); + } else { + + menu.findItem(R.id.epg_item_menu_timer_add).setVisible(false); + menu.findItem(R.id.epg_item_menu_timer_modify).setVisible(true); + menu.findItem(R.id.epg_item_menu_timer_delete).setVisible(true); + final MenuItem enableMenuItem = menu + .findItem(R.id.epg_item_menu_timer_toggle); + enableMenuItem.setVisible(true); + enableMenuItem + .setTitle(timer.isEnabled() ? R.string.epg_item_menu_timer_disable + : R.string.epg_item_menu_timer_enable); + } + + + } + + protected Timer createTimer(EventListItem item) { + Event e = item.getEvent(); + if (e instanceof Timerable == false) { + return null; + } + return ((Timerable) e).createTimer(); + + } + + /** + * Extract a Timer from a given {@link EventListItem} + * + * @param item + * @return Timer if any on the event + */ + protected Timer getTimer(EventListItem item) { + Event e = item.getEvent(); + if (e instanceof Timerable == false) { + return null; + } + return ((Timerable) e).getTimer(); + } + + protected void toggleTimer(final Timer timer) { + final ToggleTimerTask task = new ToggleTimerTask(this, timer) { + @Override + public void finished(SvdrpEvent event) { + timerModified(timer); + restoreViewSelection(); + } + }; + task.start(); + } + + /** + * Delete a given timer + * + * @param timer + */ + protected void deleteTimer(final Timer timer) { + // backupViewSelection(); + final DeleteTimerTask task = new DeleteTimerTask(this, timer) { + @Override + public void finished(SvdrpEvent event) { + timerModified(timer); + restoreViewSelection(); + } + }; + task.start(); + } + + protected void timerModified() { + timerModified(null); + } + + /** + * Is called, if a timer has been changed and so update of the list is + * required + */ + protected void timerModified(final Timer timer) { + backupViewSelection(); + if(timer != null && timer.getChannelId()!=null){ + EpgCache.CACHE.remove(timer.getChannelId()); + } + // say(R.string.update_will_start_in); + // Runnable task = new Runnable() { + // public void run() { + refresh(); + + // } + // }; + // worker.schedule(task, 1000, TimeUnit.MILLISECONDS); + } + + @Override + protected void onResume() { + super.onResume(); + } + + /* + * (non-Javadoc) + * + * @see android.app.Activity#onActivityResult(int, int, + * android.content.Intent) + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode != RESULT_OK) { + return; + } + if (requestCode == TimerDetailsActivity.REQUEST_CODE_TIMER_EDIT) { + timerModified(); + return; + } + if (requestCode == TimerDetailsActivity.REQUEST_CODE_TIMER_MODIFIED) { + timerModified(); + return; + } + + if (requestCode == TimerDetailsActivity.REQUEST_CODE_TIMER_ADD) { + timerModified(); + return; + } + + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/CertificateProblemDialog.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/CertificateProblemDialog.java new file mode 100644 index 0000000..b0c58df --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/CertificateProblemDialog.java @@ -0,0 +1,105 @@ +package de.bjusystems.vdrmanager.gui; + +import java.security.cert.X509Certificate; +import java.util.concurrent.Semaphore; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.utils.svdrp.CertificateProblemListener; + +public class CertificateProblemDialog implements CertificateProblemListener { + + /** Context */ + private final Activity activity; + /** User wanted action */ + CertificateProblemAction action; + + /** + * Constructor + * @param activity Context + */ + public CertificateProblemDialog(final Activity activity) { + this.activity = activity; + } + + @Override + public CertificateProblemAction reportProblem(final X509Certificate[] chain, final String authType) { + + // Semaphore to implement a modal dialog + final Semaphore semaphore = new Semaphore(0, true); + + // certificate properties + final String[] values = chain[0].getSubjectDN().getName().split(","); + String host = "???"; + for(String value : values) { + if (value.contains("CN=")) { + host = value.replace("CN=", "").trim(); + break; + } + } + final String creationDate = chain[0].getNotBefore().toLocaleString(); + final String validUntil = chain[0].getNotAfter().toLocaleString(); + + // message + final CharSequence message = String.format(activity.getString(R.string.certificate_problem_message_text), host, creationDate, validUntil); + + // create dialog builder + final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(activity); + alertDialogBuilder.setTitle(R.string.certificate_problem_message_title); + alertDialogBuilder.setMessage(message); + alertDialogBuilder.setCancelable(false); + + // buttons + alertDialogBuilder.setNegativeButton(R.string.certificate_not_accepted, new OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + action = CertificateProblemAction.ABORT; + dialog.cancel(); + semaphore.release(); + } + }); + alertDialogBuilder.setNeutralButton(R.string.certificate_accept_once, new OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + action = CertificateProblemAction.ACCEPT_ONCE; + dialog.cancel(); + semaphore.release(); + } + }); + alertDialogBuilder.setPositiveButton(R.string.certificate_accepted_forever, new OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int which) { + action = CertificateProblemAction.ACCEPT_FOREVER; + dialog.cancel(); + semaphore.release(); + } + }); + + // show the dialog + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + final AlertDialog dialog = alertDialogBuilder.create(); + dialog.show(); + } + }); + + + try { + semaphore.acquire(); + } catch (final InterruptedException e) { + // NOP + } + + return action; + } + + @Override + public Activity getCurrentActivity() { + return activity; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelAdapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelAdapter.java new file mode 100644 index 0000000..289dc63 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelAdapter.java @@ -0,0 +1,258 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import android.content.Context; +import android.util.Pair; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.TextView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.utils.svdrp.ChannelClient; + +class ChannelAdapter extends BaseExpandableListAdapter implements Filterable// , +// SectionIndexer +{ + @Override + public boolean areAllItemsEnabled() { + return true; + } + + private Context context; + + Map<String, ArrayList<Channel>> channels = new HashMap<String, ArrayList<Channel>>(); + + ArrayList<String> groups = new ArrayList<String>(); + + private boolean showChannelNumber; + + public ChannelAdapter(Context context) { + this.context = context; + inflater = LayoutInflater.from(context); + this.showChannelNumber = Preferences.get().isShowChannelNumbers(); + } + + private LayoutInflater inflater; + + private int groupBy = -1; + + private boolean reverse = false; + + public void fill(ArrayList<String> groups, + Map<String, ArrayList<Channel>> data, int groupBy, boolean reverse) { + this.groupBy = groupBy; + this.groups.clear(); + this.groups.addAll(groups); + channels.clear(); + channels.putAll(data); + notifyDataSetChanged(); + + } + + public Object getChild(int groupPosition, int childPosition) { + String gn = groups.get(groupPosition); + ArrayList<Channel> channels = this.channels.get(gn); + return channels.get(childPosition); + } + + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + // Return a child view. You can load your custom layout here. + + public View getChildView(int groupPosition, int childPosition, + boolean isLastChild, View convertView, ViewGroup parent) { + + Channel item = (Channel) getChild(groupPosition, childPosition); + + ChannelHolder itemHolder = new ChannelHolder(); + + // recycle view? + View view = convertView; + if (view == null) { + view = inflater.inflate(R.layout.child_layout, null); + itemHolder = new ChannelHolder(); + + itemHolder.name = (TextView) view.findViewById(R.id.channel_name); + itemHolder.type = (ImageView) view.findViewById(R.id.channel_type); + itemHolder.aux = (TextView) view.findViewById(R.id.channel_aux); + + view.setTag(itemHolder); + } else { + itemHolder = (ChannelHolder) view.getTag(); + } + + //view.setBackgroundColor(Color.BLACK); + + CharSequence name = item.getName(); + name = Utils.highlight(String.valueOf(name), channelFilter); + + if (showChannelNumber) { + name = item.getNumber() + " - " + name; + } + itemHolder.name.setText(name); + + if (groupBy == ChannelListActivity.MENU_PROVIDER) { + itemHolder.aux.setText(item.getGroup()); + } else if(groupBy == ChannelListActivity.MENU_GROUP) { + itemHolder.aux.setText(item.getProvider()); + } else if(groupBy== ChannelListActivity.MENU_SOURCE){ + itemHolder.aux.setText(item.getSource()); + } else { + itemHolder.aux.setText(item.getProvider()); + } + + return view; + } + + public int getChildrenCount(int groupPosition) { + String gn = groups.get(groupPosition); + ArrayList<Channel> channels = this.channels.get(gn); + return channels.size(); + } + + public String getGroup(int groupPosition) { + return groups.get(groupPosition); + } + + public int getGroupCount() { + return groups.size(); + } + + public long getGroupId(int groupPosition) { + return groupPosition; + } + + // Return a group view. You can load your custom layout here. + + public View getGroupView(int groupPosition, boolean isExpanded, + View convertView, ViewGroup parent) { + + String group = (String) getGroup(groupPosition); + + int channelCount = this.channels.get(group).size(); + + CharSequence groupDisplay = Utils.highlight(group, groupFilter); + + ChannelHolder itemHolder = new ChannelHolder(); + + // recycle view? + View view = convertView; + if (view == null) { + view = inflater.inflate(R.layout.group_layout, null); + itemHolder = new ChannelHolder(); + + itemHolder.name = (TextView) view.findViewById(R.id.group_name); + itemHolder.aux = (TextView) view.findViewById(R.id.channel_count); + // itemHolder.type = (ImageView) + // view.findViewById(R.id.channel_type); + + view.setTag(itemHolder); + } else { + itemHolder = (ChannelHolder) view.getTag(); + } + itemHolder.name.setText(groupDisplay); + itemHolder.aux.setText(String.valueOf(channelCount)); + + return view; + + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + } + + public boolean hasStableIds() { + return true; + } + + public boolean isChildSelectable(int arg0, int arg1) { + return true; + } + + private String groupFilter = null; + + private String sourceFilter = null; + + private String channelFilter = null; + + public Filter getFilter() { + return new Filter() { + @Override + protected FilterResults performFiltering(CharSequence arg0) { + FilterResults fr = new FilterResults(); + String q = String.valueOf(arg0).toLowerCase(); + ArrayList<String> groups = new ArrayList<String>(); + HashMap<String, ArrayList<Channel>> groupChannels = new HashMap<String, ArrayList<Channel>>(); + if (groupBy == ChannelListActivity.MENU_GROUP) { + groupFilter = String.valueOf(arg0).toLowerCase(); + for (String str : ChannelClient.getChannelGroups()) { + String g = str.toLowerCase(); + if (g.indexOf(q) != -1) { + groups.add(str); + groupChannels.put(str, ChannelClient + .getGroupChannels().get(str)); + } + } + + } else if (groupBy == ChannelListActivity.MENU_PROVIDER) { + groupFilter = String.valueOf(arg0).toLowerCase(); + for (Map.Entry<String, ArrayList<Channel>> p : ChannelClient + .getProviderChannels().entrySet()) { + String pr = p.getKey(); + String g = pr.toLowerCase(); + if (g.indexOf(q) != -1) { + groups.add(pr); + groupChannels.put(pr, p.getValue()); + } + } + + } else if(groupBy == ChannelListActivity.MENU_SOURCE) { + sourceFilter = String.valueOf(arg0).toLowerCase(); + for (Map.Entry<String, ArrayList<Channel>> p : ChannelClient + .getSourceChannels().entrySet()) { + String pr = p.getKey(); + String g = pr.toLowerCase(); + if (g.indexOf(q) != -1) { + groups.add(pr); + groupChannels.put(pr, p.getValue()); + } + } + } else { + channelFilter = String.valueOf(arg0).toLowerCase(); + ArrayList<Channel> channels = new ArrayList<Channel>(); + for(Channel c : ChannelClient.getChannels()){ + String cname = c.getName(); + String tmp = cname.toLowerCase(); + if(tmp.indexOf(channelFilter) != -1){ + channels.add(c); + } + } + String fakeGroup = context.getString(R.string.groupby_name_all_channels_group); + groups.add (fakeGroup); + groupChannels.put(fakeGroup, channels); + } + fr.values = Pair.create(groups, groupChannels); + return fr; + } + + @Override + protected void publishResults(CharSequence arg0, FilterResults arg1) { + Pair<ArrayList<String>, HashMap<String, ArrayList<Channel>>> res = (Pair<ArrayList<String>, HashMap<String, ArrayList<Channel>>>) arg1.values; + fill(res.first, res.second, groupBy, reverse); + + } + }; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelEventAdapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelEventAdapter.java new file mode 100644 index 0000000..475f3e6 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelEventAdapter.java @@ -0,0 +1,22 @@ +package de.bjusystems.vdrmanager.gui; + +import android.content.Context; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventFormatter; +import de.bjusystems.vdrmanager.data.EventListItem; + +class ChannelEventAdapter extends BaseEventAdapter<EventListItem> +{ + + + public ChannelEventAdapter(final Context context) { + super(context, R.layout.epg_event_item); + hideChannelName = true; + } + + @Override + protected EventFormatter getEventFormatter(Event event) { + return new EventFormatter(event,true); + } +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelHolder.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelHolder.java new file mode 100644 index 0000000..e103449 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelHolder.java @@ -0,0 +1,10 @@ +package de.bjusystems.vdrmanager.gui; + +import android.widget.ImageView; +import android.widget.TextView; + +class ChannelHolder { + public ImageView type; + public TextView name; + public TextView aux; +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelListActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelListActivity.java new file mode 100644 index 0000000..0b51f5e --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelListActivity.java @@ -0,0 +1,637 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.ActionBarDrawerToggle; +import android.support.v4.view.GravityCompat; +import android.support.v4.widget.DrawerLayout; +import android.text.TextUtils; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ExpandableListView; +import android.widget.ExpandableListView.ExpandableListContextMenuInfo; +import android.widget.ExpandableListView.OnChildClickListener; +import android.widget.ExpandableListView.OnGroupClickListener; +import android.widget.ListView; +import android.widget.TextView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.P; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.RecenteChannel; +import de.bjusystems.vdrmanager.data.db.DBAccess; +import de.bjusystems.vdrmanager.tasks.VoidAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.ChannelClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; + +/** + * This class is used for showing what's current running on all channels + * + * @author bju + */ +public class ChannelListActivity extends + BaseActivity<Channel, ExpandableListView> implements + OnChildClickListener, OnGroupClickListener { + + private static final String TAG = ChannelListActivity.class.getName(); + + ChannelAdapter adapter; + + Preferences prefs; + + // private static final LinkedList<Channel> RECENT = new + // LinkedList<Channel>(); + + public static final int MENU_GROUP = 0; + public static final int MENU_PROVIDER = 1; + public static final int MENU_SOURCE = 2; + public static final int MENU_NAME = 3; + + public static final boolean GROUP_NATURAL = false; + + public static final boolean GROUP_REVERSE = true; + + private int groupBy; + + private boolean groupByReverse; + + final static ArrayList<String> ALL_CHANNELS_GROUP = new ArrayList<String>(1); + + @Override + protected void onResume() { + super.onResume(); + } + + + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Attach view + + setTitle(getWindowTitle()); + initFlipper(); + + groupBy = Preferences.get(this, P.CHANNELS_LAST_ORDER, MENU_GROUP); + groupByReverse = Preferences.get(this, P.CHANNELS_LAST_ORDER_REVERSE, + GROUP_NATURAL); + + adapter = new ChannelAdapter(this); + + listView = (ExpandableListView) findViewById(R.id.channel_list); + listView.setOnChildClickListener(this); + listView.setTextFilterEnabled(true); + listView.setFastScrollEnabled(true); + listView.setAdapter(adapter); + // register context menu + registerForContextMenu(listView); + + + + startChannelQuery(); + + } + + + + @Override + protected void onPause() { + super.onPause(); + } + + private void startChannelQuery() { + backupViewSelection(); + startChannelQuery(true); + } + + private void startChannelQuery(final boolean useCache) { + + if (checkInternetConnection() == false) { + return; + } + + final ChannelClient channelClient = new ChannelClient( + getCertificateProblemDialog()); + + if (useCache == false) { + ChannelClient.clearCache(); + } + + // create background task + final SvdrpAsyncTask<Channel, SvdrpClient<Channel>> task = new SvdrpAsyncTask<Channel, SvdrpClient<Channel>>( + channelClient); + + addListener(task); + // task.addSvdrpExceptionListener(this); + // task.addSvdrpResultListener(this); + // task.addSvdrpListener(this); + // task.addSvdrpFinishedListener(this); + + // start task + task.run(); + } + + static RecentChannelsAdapter RECENT_ADAPTER = null; + + static class RecentChannelsAdapter extends ArrayAdapter<Channel> { + private final Activity context; + int resId; + + public RecentChannelsAdapter(final Activity context) { + super(context, android.R.layout.simple_list_item_1); + this.context = context; + showChannelNumbers = Preferences.get().isShowChannelNumbers(); + + if (Build.VERSION.SDK_INT < 11) { + resId = android.R.layout.select_dialog_item; + } else { + resId = android.R.layout.simple_list_item_1; + } + } + + public boolean showChannelNumbers; + + @Override + public View getView(final int position, final View convertView, + final ViewGroup parent) { + // recycle view? + TextView text1; + View view = convertView; + if (view == null) { + view = this.context.getLayoutInflater().inflate(resId, null); + text1 = (TextView) view.findViewById(android.R.id.text1); + view.setTag(text1); + } else { + text1 = (TextView) view.getTag(); + } + + final Channel c = getItem(position); + String text = showChannelNumbers ? text = c.toString() : c + .getName(); + text1.setText(text); + return view; + + } + } + + private ArrayAdapter<Channel> getRecentAdapter() { + if (RECENT_ADAPTER != null) { + RECENT_ADAPTER.showChannelNumbers = Preferences.get() + .isShowChannelNumbers(); + RECENT_ADAPTER.notifyDataSetChanged(); + return RECENT_ADAPTER; + } + + RECENT_ADAPTER = new RecentChannelsAdapter(this); + return RECENT_ADAPTER; + + } + + private void fillAdapter() { + switch (groupBy) { + case MENU_GROUP: + final ArrayList<String> cgs = ChannelClient.getChannelGroups(); + adapter.fill(cgs, ChannelClient.getGroupChannels(), groupBy, + groupByReverse); + if (cgs.size() == 1) {// one group or first no first group + listView.expandGroup(0); + } else if ((cgs.size() > 1 && TextUtils.isEmpty(cgs.get(0)))) { + listView.expandGroup(0); + } + updateWindowTitle(); + break; + + case MENU_SOURCE: + final ArrayList<String> css = ChannelClient.getChannelSources(); + adapter.fill(css, ChannelClient.getSourceChannels(), groupBy, + groupByReverse); + if (css.size() == 1) {// one group or first no first group + listView.expandGroup(0); + } else if ((css.size() > 1 && TextUtils.isEmpty(css.get(0)))) { + listView.expandGroup(0); + } + updateWindowTitle(); + break; + + case MENU_PROVIDER: + final ArrayList<String> gs = new ArrayList<String>(ChannelClient + .getProviderChannels().keySet()); + adapter.fill(gs, ChannelClient.getProviderChannels(), groupBy, + groupByReverse); + if (gs.size() == 1) { + listView.expandGroup(0); + } + updateWindowTitle(); + break; + case MENU_NAME: + if (ALL_CHANNELS_GROUP.isEmpty()) { + ALL_CHANNELS_GROUP + .add(getString(R.string.groupby_name_all_channels_group)); + } + final HashMap<String, ArrayList<Channel>> channels = new HashMap<String, ArrayList<Channel>>( + 1); + ArrayList<Channel> channelsSorted = ChannelClient.getChannels(); + Collections.sort(channelsSorted, new Comparator<Channel>() { + + @Override + public int compare(Channel lhs, Channel rhs) { + String lhsn = lhs.getName(); + String rhsn = rhs.getName(); + if (lhsn == null) { + return 1; + } + if (rhsn == null) { + return -1; + } + return lhsn.compareToIgnoreCase(rhsn); + } + }); + channels.put(getString(R.string.groupby_name_all_channels_group), + channelsSorted); + adapter.fill(ALL_CHANNELS_GROUP, channels, groupBy, groupByReverse); + listView.expandGroup(0); + updateWindowTitle(); + } + adapter.notifyDataSetChanged(); + } + + @Override + public final boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.channellist, menu); + + return true; + } + + private int getAvailableGroupByEntries() { + return R.array.channels_group_by; + } + + AlertDialog groupByDialog = null; + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + + switch (item.getItemId()) { + case R.id.channels_groupby: + // case MENU_PROVIDER: + // case MENU_NAME: + if (groupByDialog == null) { + groupByDialog = new AlertDialog.Builder(this) + .setTitle(R.string.menu_groupby) + .setIcon(android.R.drawable.ic_menu_sort_alphabetically) + .setSingleChoiceItems(getAvailableGroupByEntries(), + groupBy, new DialogInterface.OnClickListener() { + @Override + public void onClick( + final DialogInterface dialog, + final int which) { + + final boolean reversed = which == groupBy ? true + : false; + groupBy = which; + new VoidAsyncTask() { + + @Override + protected Void doInBackground( + final Void... params) { + + if (reversed) { + if (groupByReverse == true) { + groupByReverse = false; + } else { + groupByReverse = true; + } + Preferences + .set(ChannelListActivity.this, + P.CHANNELS_LAST_ORDER_REVERSE, + groupByReverse); + + } else { + Preferences + .set(ChannelListActivity.this, + P.CHANNELS_LAST_ORDER, + groupBy); + } + return null; + } + }.execute(); + + fillAdapter(); + groupByDialog.dismiss(); + } + }).create(); + } + + groupByDialog.show(); + + return true; + case R.id.channels_recent_channels: + + final String order = Preferences.get(ChannelListActivity.this, + "gui_recent_channels_order", "most"); + + List<RecenteChannel> rcs = null; + + if (order.equals("most")) { + rcs = DBAccess + .get(ChannelListActivity.this) + .getRecentChannelDAO() + .loadByRecentUse( + Preferences.get().getMaxRecentChannels()); + } else if (order.equals("last")) { + rcs = DBAccess + .get(ChannelListActivity.this) + .getRecentChannelDAO() + .loadByLastAccess( + Preferences.get().getMaxRecentChannels()); + } else { + return true; + } + + if (rcs.isEmpty()) { + say(R.string.recent_channels_no_history); + return true; + } + + if (Preferences.get().getMaxRecentChannels() <= 0) { + say(R.string.recent_channels_no_history); + return true; + } + + final ArrayAdapter<Channel> recentAdapter = getRecentAdapter(); + + recentAdapter.clear(); + for (final Channel c : DBAccess.get(ChannelListActivity.this) + .getRecentChannelDAO() + .getRecentChannels(ChannelClient.getIdChannels(), rcs)) { + recentAdapter.add(c); + } + + new AlertDialog.Builder(this) + .setTitle(R.string.recent_channels) + .setAdapter(getRecentAdapter(), + new DialogInterface.OnClickListener() { + + @Override + public void onClick( + final DialogInterface dialog, + final int which) { + final Channel c = recentAdapter + .getItem(which); + startChannelEPG(c); + } + })// + .create().show(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onCreateContextMenu(final ContextMenu menu, final View v, + final ContextMenuInfo menuInfo) { + final ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; + final int type = ExpandableListView + .getPackedPositionType(info.packedPosition); + final int group = ExpandableListView + .getPackedPositionGroup(info.packedPosition); + final int child = ExpandableListView + .getPackedPositionChild(info.packedPosition); + // Only create a context menu for child items + if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { + // Array created earlier when we built the expandable list + final Channel item = (Channel) adapter.getChild(group, child); + // if (v.getId() == R.id.channel_list) { + + final MenuInflater inflater = getMenuInflater(); + menu.setHeaderTitle(item.getName()); + inflater.inflate(R.menu.channel_list_item_menu, menu); + + } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + /* + * http://projects.vdr-developer.org/issues/722 String grp = + * adapter.getGroup(group); final MenuInflater infl = + * getMenuInflater(); menu.setHeaderTitle(grp); + * infl.inflate(R.menu.channel_list_group_menu, menu); + */ + } + } + + @Override + public boolean onContextItemSelected(final MenuItem item) { + + final ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item + .getMenuInfo(); + + // String title = ((TextView) info.targetView).getText().toString(); + + final int type = ExpandableListView + .getPackedPositionType(info.packedPosition); + + Channel channel = null; + if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { + final int groupPos = ExpandableListView + .getPackedPositionGroup(info.packedPosition); + final int childPos = ExpandableListView + .getPackedPositionChild(info.packedPosition); + channel = (Channel) adapter.getChild(groupPos, childPos); + switch (item.getItemId()) { + // case R.id.channel_item_menu_epg: + // startChannelEPG(channel); + // break; + case R.id.channel_item_menu_stream: + // show live stream + Utils.stream(this, channel); + break; + + // case R.id.channel_item_menu_hide: + // TODO http://projects.vdr-developer.org/issues/722 + // break; + // case R.id.channel_item_menu_hide_permanent: + // TODO http://projects.vdr-developer.org/issues/722 + // break; + + case R.id.channel_item_menu_switch: + Utils.switchTo(this, channel); + break; + } + + return true; + } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { + final int groupPos = ExpandableListView + .getPackedPositionGroup(info.packedPosition); + + return true; + } + + return false; + + } + + @Override + public boolean onSearchRequested() { + final InputMethodManager inputMgr = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + inputMgr.toggleSoftInput(0, 0); + return true; + } + + @Override + public boolean onGroupClick(final ExpandableListView arg0, final View arg1, + final int arg2, final long arg3) { + return true; + } + + private void startChannelEPG(final Channel channel) { + new VoidAsyncTask() { + + @Override + protected Void doInBackground(final Void... arg0) { + final int max = Preferences.get().getMaxRecentChannels(); + if (max <= 0) { + return null; + } + + DBAccess.get(ChannelListActivity.this).getRecentChannelDAO() + .hit(channel.getId()); + + return null; + } + }.execute((Void) null); + // for(int i = 0; i < recent) + // find and remember item + // final Channel channel = adapter.getItem(position); + // final VdrManagerApp app = (VdrManagerApp) getApplication(); + // app.setCurrentChannel(channel); + + // show details + final Intent intent = new Intent(); + getApp().setCurrentChannel(channel); + // intent.putExtra(Intents.CURRENT_CHANNEL, channel); + intent.setClass(this, EventEpgListActivity.class); + startActivity(intent); + } + + @Override + public boolean onChildClick(final ExpandableListView parent, final View v, + final int groupPosition, final int childPosition, final long id) { + final Channel channel = (Channel) adapter.getChild(groupPosition, + childPosition); + startChannelEPG(channel); + return false; + } + + @Override + protected void refresh() { + backupViewSelection(); + startChannelQuery(false); + } + + @Override + protected void retry() { + refresh(); + } + + @Override + protected int getMainLayout() { + return R.layout.channel_list; + } + + private String resolveWindowTitle() { + final StringBuilder sb = new StringBuilder(); + switch (groupBy) { + case MENU_NAME: + sb.append(getString(R.string.action_menu_channels)) + .append(" > ") + .append(getString(R.string.groupby_name_all_channels_group)); + break; + case MENU_PROVIDER: + sb.append(getString(R.string.action_menu_channels)) + .append(" > ") + .append(getString(R.string.groupby_window_title_templte, + getString(R.string.groupby_provider))); + break; + case MENU_GROUP: + sb.append(getString(R.string.action_menu_channels)) + .append(" > ") + .append(getString(R.string.groupby_window_title_templte, + getString(R.string.groupby_group))); + break; + + case MENU_SOURCE: { + sb.append(getString(R.string.action_menu_channels)) + .append(" > ") + .append(getString(R.string.groupby_window_title_templte, + getString(R.string.groupby_source))); + break; + } + } + + return sb.toString(); + } + + private void updateWindowTitle() { + setTitle(getString(R.string.channels_window_title_count, + resolveWindowTitle(), adapter.groups.size(), ChannelClient + .getChannels().size())); + } + + @Override + protected synchronized boolean finishedSuccess(final List<Channel> results) { + fillAdapter(); + restoreViewSelection(); + updateWindowTitle(); + return ChannelClient.getChannels().isEmpty() == false; + } + + @Override + protected void cacheHit() { + fillAdapter(); + restoreViewSelection(); + } + + @Override + protected String getWindowTitle() { + return resolveWindowTitle(); + } + + @Override + protected boolean displayingResults() { + return ChannelClient.getChannels().isEmpty() == false; + } + + @Override + protected int getProgressTextId() { + return R.string.progress_channels_loading; + } + + @Override + protected int getListNavigationIndex() { + return LIST_NAVIGATION_CHANNELS; + } +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelListFragment.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelListFragment.java new file mode 100644 index 0000000..0ce4cc2 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ChannelListFragment.java @@ -0,0 +1,8 @@ +package de.bjusystems.vdrmanager.gui; + +import android.support.v4.app.Fragment; + +public class ChannelListFragment extends Fragment { + + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ColoredButton.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ColoredButton.java new file mode 100644 index 0000000..f10860c --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/ColoredButton.java @@ -0,0 +1,92 @@ +package de.bjusystems.vdrmanager.gui; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.CornerPathEffect; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.StateListDrawable; +import android.graphics.drawable.shapes.RectShape; +import android.os.Build; +import android.util.AttributeSet; +import android.widget.Button; + +import de.bjusystems.vdrmanager.R; + +/** + * Created by lado on 03.05.15. + */ +public class ColoredButton extends Button { + + +private final float defaultRadius = 0.0f; + +private int defaultPrimaryColor; + + public ColoredButton(Context context) { + this(context, null); + } + + public ColoredButton(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + + //@TargetApi(Build.VERSION_CODES.JELLY_BEAN) + public ColoredButton(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + //defaultPrimaryColor = getResources().getColor(R.color.colorPrimary); + + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ColoredButton); + int primaryColor = typedArray.getColor(R.styleable.ColoredButton_normalStateColor, defaultPrimaryColor); + float radius = typedArray.getDimension(R.styleable.ColoredButton_cornerRadius, defaultRadius); + + int pressedStateColor = primaryColor & 0x00ffffff | 0x96000000; + ShapeDrawable shapeSelected = new ShapeDrawable(new RectShape()); + shapeSelected.getPaint().setColor(pressedStateColor); + shapeSelected.getPaint().setPathEffect(new CornerPathEffect(radius)); + shapeSelected.getPaint().setAntiAlias(true); + shapeSelected.getPaint().setStyle(Paint.Style.FILL_AND_STROKE); + shapeSelected.getPaint().setStrokeWidth(1); + + + ShapeDrawable darkenSelected = new ShapeDrawable(new RectShape()); + darkenSelected.getPaint().setColor(Color.BLACK); + darkenSelected.getPaint().setPathEffect(new CornerPathEffect(radius)); + darkenSelected.getPaint().setAntiAlias(true); + if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + darkenSelected.getPaint().setXfermode(new PorterDuffXfermode(PorterDuff.Mode.OVERLAY)); + } + darkenSelected.getPaint().setStyle(Paint.Style.FILL_AND_STROKE); + darkenSelected.getPaint().setStrokeWidth(1); + + + LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{darkenSelected, shapeSelected}); + + ShapeDrawable shapeNormal = new ShapeDrawable(new RectShape()); + shapeNormal.getPaint().setAntiAlias(true); + shapeNormal.getPaint().setColor(primaryColor); + shapeNormal.getPaint().setPathEffect(new CornerPathEffect(radius)); + shapeNormal.getPaint().setStyle(Paint.Style.FILL_AND_STROKE); + shapeNormal.getPaint().setStrokeWidth(1); + + StateListDrawable states = new StateListDrawable(); + //Resources res = getResources(); + states.addState(new int[]{android.R.attr.state_pressed}, layerDrawable); + states.addState(new int[]{android.R.attr.state_focused}, layerDrawable); + states.addState(new int[]{}, shapeNormal); + + if( Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN){ + setBackground(states); + } else { + setBackgroundDrawable(states); + } + //typedArray.recycle(); + } +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EpgDetailsActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EpgDetailsActivity.java new file mode 100644 index 0000000..c1daefa --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EpgDetailsActivity.java @@ -0,0 +1,768 @@ +package de.bjusystems.vdrmanager.gui; + +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +import android.app.AlertDialog; +import android.app.SearchManager; +import android.content.ActivityNotFoundException; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.v4.view.PagerAdapter; +import android.support.v4.view.ViewPager; +import android.support.v4.view.ViewPager.OnPageChangeListener; +import android.support.v7.app.ActionBarActivity; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.viewpagerindicator.TitleProvider; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.Intents; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.data.Epg; +import de.bjusystems.vdrmanager.data.EpgCache; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventFormatter; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Recording; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.data.TimerMatch; +import de.bjusystems.vdrmanager.data.Timerable; +import de.bjusystems.vdrmanager.data.Timerable.TimerState; +import de.bjusystems.vdrmanager.tasks.CreateTimerTask; +import de.bjusystems.vdrmanager.tasks.DeleteTimerTask; +import de.bjusystems.vdrmanager.tasks.ToggleTimerTask; +import de.bjusystems.vdrmanager.tasks.VoidAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpEvent; + +/** + * This class is used for showing what's current running on all channels + * + * @author bju + */ +public class EpgDetailsActivity extends ActionBarActivity implements + OnClickListener, OnPageChangeListener { + + public static final String TAG = "EpgDetailsActivity"; + + public static String IMDB_BASE_URL = "http://%s"; + + public static String IMDB_URL_QUERY = "/find?s=tt&q=%s"; + + public static String OMDB_URL = "http://www.omdb.org/search?search[text]=%s"; + + private static final String IMDB_URL_ENCODING = "UTF-8"; + + private static final String OMDB_URL_ENCODING = "UTF-8"; + + private static final String TMDB_URL_ENCODING = "UTF-8"; + + public static String TMDB_URL = "http://www.themoviedb.org/search?search=%s"; + + private String highlight = null; + + // private Event cEvent; + + // private ImageView state; + + private boolean modifed = false; + + // private int current; + + private ViewPager pager; + + private Adapter adapter; + + // private Timerable timerable = null; + + class Adapter extends PagerAdapter implements TitleProvider { + + public Adapter() { + + } + + public String getTitle(int position) { + return epgs.get(position).getChannelName(); + } + + public int getCount() { + return epgs.size(); + } + + public Object instantiateItem(View pager, int position) { + View view = getLayoutInflater().inflate(R.layout.epg_detail, null); + // Event e = epgs.get(position); + publishEPG(view, position); + ((ViewPager) pager).addView(view, 0); + + return view; + } + + public void destroyItem(View pager, int position, Object view) { + ((ViewPager) pager).removeView((View) view); + } + + public boolean isViewFromObject(View view, Object object) { + return view.equals(object); + } + + public void finishUpdate(View view) { + } + + public void restoreState(Parcelable p, ClassLoader c) { + } + + public Parcelable saveState() { + return null; + } + + public void startUpdate(View view) { + } + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getSupportActionBar().setHomeButtonEnabled(true); + // requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + + Intent i = getIntent(); + + highlight = i.getStringExtra(Intents.HIGHLIGHT); + final int preselect = i.getIntExtra(Intents.CURRENT_EPG, 0); + + + + // requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); + // getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, + // R.layout.titlebar); + + // Attach view + setContentView(R.layout.epgdetails); + + // detector = new SimpleGestureFilter(this, this); + + // state = (ImageView) findViewById(R.id.epg_timer_state); + + final Event epg = getApp().getCurrentEvent(); + if (epg == null) { + finish(); + } + + final Event cEvent = epg; + + if (epg instanceof Timerable) { + // timerable = (Timerable) cEvent; + } + + + pager = (ViewPager) findViewById(R.id.viewpager); + pager.setOnPageChangeListener(this); + + + new VoidAsyncTask() { + + int counter = 0; + + @Override + protected void onPreExecute() { + setProgressBarIndeterminateVisibility(true); + } + + @Override + protected Void doInBackground(Void... params) { + // current event + final VdrManagerApp app = (VdrManagerApp) getApplication(); + epgs = app.getCurrentEpgList(); + + if (epgs.isEmpty()) { + epgs.add(cEvent); + return (Void) null; + } + + // for (Event e : epgs) { + // if (epg.equals(e)) { + // break; + //} + //counter++; + //} + + //if (counter == epgs.size()) {// not found? + //epgs.add(0, cEvent); + //counter = 0; + //} + if(preselect < epgs.size()){ + counter = preselect; + } + return (Void) null; + } + + @Override + protected void onPostExecute(Void result) { + adapter = new Adapter(); + pager.setAdapter(adapter); + pager.setCurrentItem(counter); + onPageSelected(counter); + } + }.execute((Void) null); + + } + + private void setState(ImageView view, int res) { + view.setVisibility(View.VISIBLE); + view.setImageResource(res); + } + + private static String encode(String str, String enc) { + try { + return URLEncoder.encode(str, enc); + } catch (Exception ex) { + Log.w(TAG, ex); + return URLEncoder.encode(str); + } + } + + public void publishEPG(final View view, int position) { + + Event event = epgs.get(position); + + Timerable timerable = null; + + if (event instanceof Timerable) { + timerable = (Timerable) event; + } + + view.setTag(event); + // view.setTag(event); + + final EventFormatter formatter = new EventFormatter(event); + + final TextView title = (TextView) view + .findViewById(R.id.epg_detail_title); + String titleText = formatter.getTitle(); + title.setText(Utils.highlight(titleText, highlight)); + // title.setTextSize(TypedValue.COMPLEX_UNIT_PX, title.getTextSize() + // * (float) 1.3); + + ((TextView) view.findViewById(R.id.epg_detail_time)).setText(formatter + .getDate() + " " + formatter.getTime()); + + TextView dura = (TextView) view.findViewById(R.id.epg_detail_duration); + + ((TextView) view.findViewById(R.id.epg_detail_channel)).setText(event + .getChannelName()); + // ((TextView) findViewById(R.id.epg_detail_date)).setText(formatter + // .getLongDate()); + ImageView state = (ImageView) view.findViewById(R.id.epg_timer_state); + if (timerable == null) { + setState(state, R.drawable.timer_none); + } else { + + TimerMatch match = timerable.getTimerMatch(); + + switch (timerable.getTimerState()) { + case Active: + setState(state, Utils.getTimerStateDrawable(match, + R.drawable.timer_active, + R.drawable.timer_active_begin, + R.drawable.timer_active_end, + R.drawable.timer_active_conflict)); + break; + case Inactive: + setState(state, Utils.getTimerStateDrawable(match, + R.drawable.timer_inactive, + R.drawable.timer_inactive_begin, + R.drawable.timer_inactive_end, + R.drawable.timer_inactive)); + break; + case Recording: + setState(state, Utils.getTimerStateDrawable(match, + R.drawable.timer_recording, + R.drawable.timer_recording_begin, + R.drawable.timer_recording_end, + R.drawable.timer_recording_conflict)); + break; + default: + setState(state, R.drawable.timer_none); + } + } + final TextView shortText = (TextView) view + .findViewById(R.id.epg_detail_shorttext); + shortText.setText(Utils.highlight(formatter.getShortText(), highlight)); + + final TextView textView = (TextView) view + .findViewById(R.id.epg_detail_description); + textView.setText(Utils.highlight(formatter.getDescription(), highlight)); + + if (event.getAudio().isEmpty() == false) { + view.findViewById(R.id.audio_block).setVisibility(View.VISIBLE); + final TextView audioTracks = (TextView) view + .findViewById(R.id.epg_detail_audio); + audioTracks.setText(Utils.formatAudio(this, event.getAudio())); + } else { + view.findViewById(R.id.audio_block).setVisibility(View.GONE); + } + + TextView contentView = ((TextView) view + .findViewById(R.id.epg_detail_cats)); + if (event.getContent().length > 0) { + contentView.setVisibility(View.VISIBLE); + contentView + .setText(Utils.getContenString(this, event.getContent())); + } else { + contentView.setVisibility(View.GONE); + } + + // copy color for separator lines + // final int color = textView.getTextColors().getDefaultColor(); + // ((TextView) findViewById(R.id.epg_detail_separator_1)) + // .setBackgroundColor(color); + + int p = Utils.getProgress(event); + + ((ProgressBar) view.findViewById(R.id.epg_detail_progress)) + .setProgress(p); + int dm = Utils.getDuration(event); + if (Utils.isLive(event)) { + int rest = dm - (dm * p / 100); + dura.setText(getString(R.string.epg_duration_template_live, rest, + dm)); + } else { + dura.setText(getString(R.string.epg_duration_template, dm)); + } + + // ((TextView) view.findViewById(R.id.epg_detail_separator_2)) + // .setBackgroundColor(color); + + // register button handler + if (timerable == null) { + view.findViewById(R.id.epg_event_create_timer).setVisibility( + View.GONE); + } else { + setThisAsOnClickListener(view, R.id.epg_event_create_timer); + } + + View b = view.findViewById(R.id.epg_event_imdb); + + if (Preferences.get().isShowImdbButton() == false) { + b.setVisibility(View.GONE); + } else { + b.setVisibility(View.VISIBLE); + b.setOnClickListener(new OnClickListener() { + + public void onClick(View v) { + startFilmDatabaseBrowseIntent( + String.format(IMDB_BASE_URL, Preferences.get() + .getImdbUrl()) + + IMDB_URL_QUERY, view, IMDB_URL_ENCODING); + } + }); + } + + b = view.findViewById(R.id.epg_event_omdb); + + if (Preferences.get().isShowOmdbButton() == false) { + b.setVisibility(View.GONE); + } else { + b.setVisibility(View.VISIBLE); + b.setOnClickListener(new OnClickListener() { + + public void onClick(View v) { + startFilmDatabaseBrowseIntent(OMDB_URL, view, + OMDB_URL_ENCODING); + } + }); + } + + b = view.findViewById(R.id.epg_event_tmdb); + + if (Preferences.get().isShowTmdbButton() == false) { + b.setVisibility(View.GONE); + } else { + b.setVisibility(View.VISIBLE); + b.setOnClickListener(new OnClickListener() { + + public void onClick(View v) { + startFilmDatabaseBrowseIntent(TMDB_URL, view, + TMDB_URL_ENCODING); + } + }); + } + + b = view.findViewById(R.id.epg_event_livetv); + if (Utils.isLive(event) == false + && (event instanceof Recording == false || Preferences.get() + .isEnableRecStream() == false)) { + b.setVisibility(View.GONE); + } else { + b.setVisibility(View.VISIBLE); + setThisAsOnClickListener(b); + } + // setThisAsOnClickListener(view, R.id.epg_event_left); + // setThisAsOnClickListener(view, R.id.epg_event_right); + + // set button text + if (event instanceof Timer) { + // timeButton.setText(R.string.epg_event_create_timer_text); + } else { + // timeButton.setText(R.string.epg_event_modify_timer_text); + } + + } + + private void startFilmDatabaseBrowseIntent(String url, View view, + String encoding) { + final TextView title = (TextView) view + .findViewById(R.id.epg_detail_title); + url = String.format(url, + encode(String.valueOf(title.getText()), encoding)); + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + i.addCategory(Intent.CATEGORY_BROWSABLE); + try { + startActivity(i); + } catch (ActivityNotFoundException anfe) { + Log.w(TAG, anfe); + say(anfe.getLocalizedMessage()); + } + } + + private void setThisAsOnClickListener(View v) { + if (v != null) { + v.setOnClickListener(this); + } + } + + private void setThisAsOnClickListener(View root, int view) { + setThisAsOnClickListener(root.findViewById(view)); + } + + @Override + protected void onResume() { + super.onResume(); + // TODO Check here whether the config has changed for imdb + } + + @Override + protected void onPause() { + super.onPause(); + } + + class Wrapper { + public int id; + public String value; + + public Wrapper(int id) { + this.id = id; + this.value = getString(id); + } + + public String toString() { + return value; + } + } + + public Timer getTimer(Event event) { + if (event instanceof Timer) { + return (Timer) event; + } + if (event instanceof Epg) { + return ((Epg) event).getTimer(); + } + return null; + } + + protected VdrManagerApp getApp() { + final VdrManagerApp app = (VdrManagerApp) getApplication(); + return app; + } + + public void onClick(final View v) { + + final Event cEvent = epgs.get(pager.getCurrentItem()); + + switch (v.getId()) { + case R.id.epg_event_livetv: + if (cEvent instanceof Recording) { + Utils.streamRecording(this, (Recording) cEvent); + } else { + Utils.stream(this, String.valueOf(cEvent.getChannelNumber())); + } + break; + case R.id.epg_event_create_timer: + final ArrayAdapter<Wrapper> ada = new ArrayAdapter<Wrapper>(this, + android.R.layout.simple_dropdown_item_1line); + final Timer timer = getTimer(cEvent); + TimerMatch tm = Utils.getTimerMatch(cEvent, timer); + // remove unneeded menu items + if (timer != null && tm == TimerMatch.Full) { + ada.add(new Wrapper(R.string.epg_item_menu_timer_modify)); + ada.add(new Wrapper(R.string.epg_item_menu_timer_delete)); + if (timer.isEnabled()) { + ada.add(new Wrapper(R.string.epg_item_menu_timer_disable)); + } else { + ada.add(new Wrapper(R.string.epg_item_menu_timer_enable)); + } + } else if (cEvent instanceof Recording) { + ada.add(new Wrapper(R.string.epg_item_menu_timer_delete)); + } else { + + ada.add(new Wrapper(R.string.epg_item_menu_timer_add)); + if (Utils.isLive(cEvent) && (cEvent instanceof Timerable) + && ((Timerable) cEvent).getTimer() == null) { + ada.add(new Wrapper(R.string.epg_item_menu_timer_record)); + } + } + + final Timerable timerable; + + if (cEvent instanceof Timerable) { + timerable = (Timerable) cEvent; + } else { + return; + } + + new AlertDialog.Builder(this) + .setAdapter(ada, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + Wrapper w = ada.getItem(which); + switch (w.id) { + case R.string.epg_item_menu_timer_add: { + getApp().setCurrentTimer( + timerable.createTimer()); + final Intent intent = new Intent(); + intent.setClass(EpgDetailsActivity.this, + TimerDetailsActivity.class); + intent.putExtra(Intents.TIMER_OP, + Intents.ADD_TIMER); + startActivityForResult( + intent, + TimerDetailsActivity.REQUEST_CODE_TIMER_ADD); + break; + } + case R.string.epg_item_menu_timer_modify: { + getApp().setCurrentTimer(timer); + final Intent intent = new Intent(); + intent.setClass(EpgDetailsActivity.this, + TimerDetailsActivity.class); + intent.putExtra(Intents.TIMER_OP, + Intents.EDIT_TIMER); + startActivityForResult( + intent, + TimerDetailsActivity.REQUEST_CODE_TIMER_EDIT); + break; + } + case R.string.epg_item_menu_timer_delete: { + deleteTimer(timer); + break; + } + case R.string.epg_item_menu_timer_enable: + case R.string.epg_item_menu_timer_disable: { + toggleTimer(timer); + break; + } + + case R.string.epg_item_menu_timer_record: { + final Timer timer = new Timer(cEvent); + final CreateTimerTask task = new CreateTimerTask( + EpgDetailsActivity.this, timer) { + @Override + public void finished(SvdrpEvent event) { + modifed = true; + EpgCache.CACHE.remove(timer + .getChannelId()); + say(R.string.recording_started); + } + }; + task.start(); + + } + } + } + }).create()// + .show();// + + break; + // case R.id.epg_event_imdb: + + // break; + + // case R.id.epg_event_share: + // shareEvent(cEvent); + // break; + } + } + + protected void toggleTimer(final Timer timer) { + final ToggleTimerTask task = new ToggleTimerTask(this, timer) { + @Override + public void finished(SvdrpEvent event) { + if (event == SvdrpEvent.FINISHED_SUCCESS) { + TimerState state = timer.getTimerState(); + TimerMatch match = timer.getTimerMatch(); + int res = -1; + if (state == TimerState.Active) { + res = Utils.getTimerStateDrawable(match, + R.drawable.timer_inactive, + R.drawable.timer_inactive_begin, + R.drawable.timer_inactive_end, + R.drawable.timer_inactive); + } else if (state == TimerState.Inactive) { + res = Utils.getTimerStateDrawable(match, + R.drawable.timer_active, + R.drawable.timer_active_begin, + R.drawable.timer_active_end, + R.drawable.timer_active_conflict); + + } + if (res != -1) { + setState( + (ImageView) findViewById(R.id.epg_timer_state), + res); + } + } + } + }; + task.start(); + } + + private List<Event> epgs = new ArrayList<Event>(); + + protected void say(int res) { + Toast.makeText(this, res, Toast.LENGTH_SHORT).show(); + } + + protected void say(String msg) { + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); + } + + @Override + public final boolean onCreateOptionsMenu( + Menu menu) { + super.onCreateOptionsMenu(menu); + + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.epg_details, menu); + + // mShareActionProvider = (ShareActionProvider) + // menu.findItem(R.id.epg_details_menu_share).getActionProvider(); + // mShareActionProvider.setShareIntent(getDefaultShareIntent()); + + return true; + } + + private void shareEvent(Event event) { + Utils.shareEvent(this, event); + } + + @Override + public boolean onOptionsItemSelected( + MenuItem item) { + + Event cEvent = epgs.get(pager.getCurrentItem()); + + if (item.getItemId() == R.id.epg_details_menu_share) { + shareEvent(cEvent); + return true; + } + + if (item.getItemId() == R.id.epg_details_menu_add_to_cal) { + Utils.addCalendarEvent(this, cEvent); + } + + if (item.getItemId() == R.id.epg_details_menu_search_repeat) { + Intent intent = new Intent(this, EpgSearchListActivity.class); + intent.setAction(Intent.ACTION_SEARCH); + intent.putExtra(SearchManager.QUERY, cEvent.getTitle()); + startActivity(intent); + return true; + } + + if (item.getItemId() == R.id.epg_details_menu_switch) { + Utils.switchTo(this, cEvent.getChannelId(), cEvent.getChannelName()); + return true; + } + return super.onOptionsItemSelected(item); + } + + protected void deleteTimer(final Timer timer) { + final DeleteTimerTask task = new DeleteTimerTask(this, timer) { + @Override + public void finished(SvdrpEvent event) { + if (event == SvdrpEvent.FINISHED_SUCCESS) { + setState((ImageView) findViewById(R.id.epg_timer_state), + R.drawable.timer_none); + modifed = true; + EpgCache.CACHE.remove(timer.getChannelId()); + } + } + }; + task.start(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != RESULT_OK) { + return; + } + + // View view = pager.getChildAt(current); + // ImageView state = (ImageView) + // view.findViewById(R.id.epg_timer_state); + + if (requestCode == TimerDetailsActivity.REQUEST_CODE_TIMER_ADD) { + modifed = true; + // setState( + // state, + // Utils.isLive(getApp().getCurrentEvent()) ? + // R.drawable.timer_recording + // : R.drawable.timer_active); + } else if (requestCode == TimerDetailsActivity.REQUEST_CODE_TIMER_EDIT) { + modifed = true; + // ?? + } + adapter.notifyDataSetChanged(); + } + + @Override + public void onBackPressed() { + if (modifed) { + setResult(RESULT_OK); + finish(); + } else { + super.onBackPressed(); + } + } + + public void onPageScrollStateChanged(int state) { + } + + public void onPageScrolled(int position, float positionOffset, + int positionOffsetPixels) { + } + + public void onPageSelected(int position) { + + Event cEvent = epgs.get(position); + String cn = cEvent.getChannelName(); + // View view = pager.getChildAt(arg0); + // state = (ImageView) view.findViewById(R.id.epg_timer_state); + setTitle(getString(R.string.epg_of_a_channel, cn, position + 1, + epgs.size())); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EpgSearchListActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EpgSearchListActivity.java new file mode 100644 index 0000000..0c08670 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EpgSearchListActivity.java @@ -0,0 +1,290 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.List; + +import android.app.SearchManager; +import android.content.Intent; +import android.os.Bundle; +import android.provider.SearchRecentSuggestions; +import android.text.TextUtils; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.data.Epg; +import de.bjusystems.vdrmanager.data.EpgSearchParams; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.data.P; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.data.db.EPGSearchSuggestionsProvider; +import de.bjusystems.vdrmanager.utils.date.DateFormatter; +import de.bjusystems.vdrmanager.utils.svdrp.EpgClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; + +/** + * This class is used for showing what's current running on all channels + * + * @author bju + */ +public class EpgSearchListActivity extends BaseTimerEditActivity<Epg> implements + OnItemClickListener { + + protected static ArrayList<Epg> CACHE = new ArrayList<Epg>(); + + @Override + protected List<Epg> getCACHE() { + return CACHE; + } + + private void initSearch(final Intent intent) { + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + final String query = intent.getStringExtra(SearchManager.QUERY); + if (TextUtils.isEmpty(query) == false) { + highlight = query.trim(); + final SearchRecentSuggestions suggestions = new SearchRecentSuggestions( + this, EPGSearchSuggestionsProvider.AUTHORITY, + EPGSearchSuggestionsProvider.MODE); + suggestions.saveRecentQuery(query, null); + } + } + } + + @Override + protected void onNewIntent(final Intent intent) { + initSearch(intent); + startSearch(); + } + + private void startSearch() { + startEpgQuery(); + } + + @Override + protected String getViewID() { + return this.getClass().getSimpleName(); + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + Preferences.setLocale(this); + // Preferences.init(this); + + super.onCreate(savedInstanceState); + + sortBy = Preferences.get(this, getViewID() + "_" + P.EPG_LAST_SORT, + MENU_GROUP_CHANNEL); + + final Intent intent = getIntent(); + initSearch(intent); + adapter = new TimeEventAdapter(this); + + // Create adapter for EPG list + adapter.setHideDescription(false); + listView = (ListView) findViewById(R.id.whatson_list); + listView.setAdapter(adapter); + listView.setTextFilterEnabled(true); + registerForContextMenu(listView); + // register EPG item click + listView.setOnItemClickListener(this); + startSearch(); + } + + public void onNothingSelected(final AdapterView<?> arg0) { + // startTimeEpgQuery(((EpgTimeSpinnerValue)timeSpinner.getAdapter().getItem(0)).getValue()); + } + + // + + private void startEpgQuery() { + + if (checkInternetConnection() == false) { + return; + } + + final EpgSearchParams sp = new EpgSearchParams(); + sp.setTitle(highlight); + setTitle(getWindowTitle()); + final EpgClient epgClient = new EpgClient(sp, + getCertificateProblemDialog()); + // remove old listeners + // epgClient.clearSvdrpListener(); + + // create background task + final SvdrpAsyncTask<Epg, SvdrpClient<Epg>> task = new SvdrpAsyncTask<Epg, SvdrpClient<Epg>>( + epgClient); + + // create progress + addListener(task); + + // start task + task.run(); + } + + protected void sort() { + /* */ + switch (sortBy) { + + case MENU_GROUP_DEFAULT: { + sortItemsByTime(CACHE, false); + break; + } + case MENU_GROUP_ALPHABET: { + Collections.sort(CACHE, new TitleComparator()); + break; + } + case MENU_GROUP_CHANNEL: { + sortItemsByChannel(CACHE); + } + } + } + + @Override + protected int getBaseMenu() { + return R.menu.refresh_menu; + } + + @Override + protected synchronized void fillAdapter() { + + adapter.highlight = this.highlight; + + adapter.clear(); + + if (CACHE.isEmpty()) { + return; + } + + sort(); + + final Calendar cal = Calendar.getInstance(); + int day = -1; + for (final Event e : CACHE) { + cal.setTime(e.getStart()); + final int eday = cal.get(Calendar.DAY_OF_YEAR); + if (eday != day) { + day = eday; + adapter.add(new EventListItem(new DateFormatter(cal) + .getDailyHeader())); + } + adapter.add(new EventListItem(e)); + } + adapter.notifyDataSetChanged(); + } + + @Override + protected int getAvailableSortByEntries() { + return R.array.epg_sort_by_time_alpha_channel; + } + + /* + * (non-Javadoc) TODO this method also should be used in startEpgQuery on + * cache hit + * + * @see de.bjusystems.vdrmanager.gui.BaseEpgListActivity#finishedSuccess() + */ + @Override + protected boolean finishedSuccessImpl(final List<Epg> results) { + + clearCache(); + for (final Epg e : results) { + CACHE.add(e); + } + pushResultCountToTitle(); + fillAdapter(); + listView.setSelectionAfterHeaderView(); + return adapter.getCount() > 0; + } + +// @Override +// protected int prepareDetailsViewData(final EventListItem item, int position) { +// final VdrManagerApp app = (VdrManagerApp) getApplication(); +// app.setCurrentEvent(item.getEvent()); +// app.setCurrentEpgList(CACHE); +// for (int i = 0; i < position; ++i) { +// if (CACHE.get(i) == item.getEvent()) { +// return i; +// } +// } +// +// return 0; +// } + + @Override + protected int getMainLayout() { + return R.layout.search_epg_list; + } + + @Override + protected void refresh() { + startEpgQuery(); + } + + @Override + protected void retry() { + startEpgQuery(); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + // MenuItem item; + // item = menu.add(MENU_GROUP_NEW_TIMER, MENU_NEW_TIMER, 0, + // R.string.new_timer); + // item.setIcon(android.R.drawable.ic_menu_add);; + // /item.setAlphabeticShortcut('r'); + + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.epg_search_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == R.id.epg_search) { + startSearchManager(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + protected String getWindowTitle() { + if (TextUtils.isEmpty(highlight)) { + return getString(R.string.epg_by_search); + } + + return getString(R.string.epg_by_search_param, highlight); + } + + // @Override + // public boolean onSearchRequested() { + // startSearchManager(); + // return true; + // } + + @Override + protected int getListNavigationIndex() { + return -1; + } + + @Override + protected boolean hasListNavigation() { + return false; + } + + @Override + protected void timerModified(final Timer timer) { + clearCache(); + super.timerModified(timer); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EpgSearchTimesListActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EpgSearchTimesListActivity.java new file mode 100644 index 0000000..9be94cb --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EpgSearchTimesListActivity.java @@ -0,0 +1,147 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.List; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TimePicker; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.EpgSearchTimeValue; +import de.bjusystems.vdrmanager.data.EpgSearchTimeValues; +import de.bjusystems.vdrmanager.data.Preferences; + +/** + * This class is used for showing what's + * current running on all channels + + * @author bju + */ +public class EpgSearchTimesListActivity extends Activity + implements OnClickListener{ + + ArrayAdapter<EpgSearchTimeValue> adapter; + + List<EpgSearchTimeValue> values; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Attach view + setContentView(R.layout.epg_search_times_list); + + setTitle(R.string.epg_search_times_window); + + // Create adapter for ListView + adapter = new ArrayAdapter<EpgSearchTimeValue>(this, R.layout.epg_search_times_item); + final ListView listView = (ListView) findViewById(R.id.epg_search_times_list); + listView.setAdapter(adapter); + registerForContextMenu(listView); + + // create channel list + updateList(); + + // button handler + final Button addButton = (Button) findViewById(R.id.epg_search_times_add); + addButton.setOnClickListener(this); + } + + public void onClick(final View v) { + // show time selection + final TimePicker timePicker = new TimePicker(this); + timePicker.setIs24HourView(Preferences.get().isUse24hFormat()); + new AlertDialog.Builder(this) + .setTitle(R.string.set_time) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + onTimeSet(timePicker.getCurrentHour(), timePicker.getCurrentMinute()); + } + }) + .setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + dialog.dismiss(); + } + }).setView(timePicker).show(); + } + + public void onTimeSet(final int hourOfDay, final int minute) { + + final EpgSearchTimeValue time = new EpgSearchTimeValue(values.size(), String.format("%02d:%02d", hourOfDay, minute)); + values.add(time); + adapter.add(time); + + save(); + + updateList(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + + if (v.getId() == R.id.epg_search_times_list) { + final MenuInflater inflater = getMenuInflater(); + final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)menuInfo; + menu.setHeaderTitle(values.get(info.position+2).toString()); + + inflater.inflate(R.menu.epg_search_time_item_menu, menu); + } + } + + + + @Override + public boolean onContextItemSelected(final MenuItem item) { + + final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo(); + final EpgSearchTimeValue time = values.get(info.position+2); + + if (item.getItemId() == R.id.epg_search_time_delete) { + values.remove(time); + adapter.remove(time); + save(); + } + + return true; + } + + private void updateList() { + + // get values + values = new EpgSearchTimeValues(this).getValues(); + adapter.clear(); + for(int i = 2; i < values.size() - 1; i++) { + adapter.add(values.get(i)); + } + } + + private void save() { + new EpgSearchTimeValues(this).saveValues(values); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EventEpgListActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EventEpgListActivity.java new file mode 100644 index 0000000..3e73729 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EventEpgListActivity.java @@ -0,0 +1,489 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import android.content.Intent; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.Epg; +import de.bjusystems.vdrmanager.data.EpgCache; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.tasks.ChannelsTask; +import de.bjusystems.vdrmanager.utils.date.DateFormatter; +import de.bjusystems.vdrmanager.utils.svdrp.ChannelClient; +import de.bjusystems.vdrmanager.utils.svdrp.EpgClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpEvent; + +/** + * This class is used for showing what's current running on all channels + * + * @author bju + */ +public class EventEpgListActivity extends BaseTimerEditActivity<Epg> implements + OnItemClickListener, OnItemSelectedListener { + + private static final String TAG = EventEpgListActivity.class + .getSimpleName(); + + // protected static Date nextForceCache = null; + + // private static Channel cachedChannel = null; + + Spinner channelSpinner; + + View switcher; + + ArrayAdapter<Channel> channelSpinnerAdapter; + + // protected static ArrayList<Epg> CACHE = new ArrayList<Epg>(); + + private TextView audio; + + private View channelInfo; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // + // + // create adapter for channel spinner + channelSpinnerAdapter = new ArrayAdapter<Channel>(this, + android.R.layout.simple_spinner_item); + channelSpinnerAdapter + .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + channelSpinner = (Spinner) findViewById(R.id.epg_list_channel_spinner); + channelSpinner.setAdapter(channelSpinnerAdapter); + + switcher = findViewById(R.id.switch_epg_view); + switcher.setOnClickListener(this); + + // add channel values + // boolean useChannelNumber = Preferences.get().isShowChannelNumbers(); + + // show needed items + + channelInfo = findViewById(R.id.channel_info); + + channelInfo.setOnClickListener(this); + + audio = (TextView) channelInfo.findViewById(R.id.channel_audio); + + adapter = new ChannelEventAdapter(this); + + // if (currentChannel != null) { + + // } + // startChannelEpgQuery(channel); + // findViewById(R.id.timer_item_channel).setVisibility(View.GONE); + // break; + + // Create adapter for EPG list + listView = (ListView) findViewById(R.id.whatson_list); + listView.setAdapter(adapter); + // listView.setFastScrollEnabled(true); + listView.setTextFilterEnabled(true); + registerForContextMenu(listView); + + // register EPG item click + listView.setOnItemClickListener(this); + + if (checkInternetConnection() == false) { + return; + } + + startQuery(); + + } + + @Override + protected int getAvailableSortByEntries() { + return R.array.epg_sort_by_time_alpha; + } + + @Override + protected String getViewID() { + return EventEpgListActivity.class.getSimpleName(); + } + + private void startQuery() { + new ChannelsTask(this, new ChannelClient(getCertificateProblemDialog())) { + @Override + public void finished(final SvdrpEvent event) { + if (event == SvdrpEvent.CACHE_HIT + || event == SvdrpEvent.FINISHED_SUCCESS) { + final ArrayList<Channel> channels = ChannelClient + .getChannels(); + currentChannel = getApp().getCurrentChannel(); + boolean found = false; + int count = 0; + for (final Channel c : channels) { + channelSpinnerAdapter.add(c); + if (currentChannel != null && !found) { + if (currentChannel.equals(c)) { + found = true; + } else { + count++; + } + } + } + channelSpinner.setSelection(count); + channelSpinner + .setOnItemSelectedListener(EventEpgListActivity.this); + } else { + noConnection(event); + } + } + }.start(); + + } + + void sort() { + if (sortBy == BaseEventListActivity.MENU_GROUP_ALPHABET) { + Collections.sort(getCache(), new TitleComparator()); + } else if (sortBy == BaseEventListActivity.MENU_GROUP_DEFAULT) { + Collections.sort(getCache(), new TimeComparator(false)); + } + } + + @Override + protected void onPause() { + super.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + public void onItemSelected(final AdapterView<?> parent, final View view, + final int position, final long id) { + // get spinner value + final Channel channel = (Channel) channelSpinner.getSelectedItem(); + currentChannel = channel; + setCurrent(channel); + // setAsCurrent(channel); + // update search + if (channel.getAudio().isEmpty() == false) { + audio.setText(Utils.formatAudio(this, channel.getAudio())); + } else { + audio.setText(""); + } + + startEpgQuery(false); + } + + @Override + public void onNothingSelected(final AdapterView<?> arg0) { + // startTimeEpgQuery(((EpgTimeSpinnerValue)timeSpinner.getAdapter().getItem(0)).getValue()); + } + + @Override + public void clearCache() { + getCache().clear(); + EpgCache.CACHE.remove(currentChannel.getId()); + EpgCache.NEXT_REFRESH.remove(currentChannel.getId()); + } + + private boolean useCache() { + + if (currentChannel == null) { + return false; + } + + final ArrayList<Epg> cachedChannel = EpgCache.CACHE.get(currentChannel + .getId()); + + if (cachedChannel == null) { + return false; + } + + final Date nextForceCache = EpgCache.NEXT_REFRESH.get(currentChannel + .getId()); + + if (nextForceCache == null) { + return false; + } + final Date now = new Date(); + if (nextForceCache.before(now)) { + return false; + } + return true; + } + + @Override + public void onClick(final View view) { + + if (view == switcher) { + final Intent intent = new Intent(); + intent.setClass(this, TimeEpgListActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT + | Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(intent); + } else if (view == channelInfo) { + Utils.stream(this, currentChannel); + } else { + super.onClick(view); + } + } + + private void startEpgQuery(final boolean force) { + if (useCache() && !force) { + fillAdapter(); + return; + } + + if (checkInternetConnection() == false) { + return; + } + + // clearCache(); + + final EpgClient epgClient = new EpgClient(currentChannel, + getCertificateProblemDialog()); + + // remove old listeners + // epgClient.clearSvdrpListener(); + + // create background task + final SvdrpAsyncTask<Epg, SvdrpClient<Epg>> task = new SvdrpAsyncTask<Epg, SvdrpClient<Epg>>( + epgClient); + + // create progress + + addListener(task); + + // start task + task.run(); + } + + private static final ArrayList<Epg> EMPTY = new ArrayList<Epg>(0); + + private ArrayList<Epg> getCache() { + if (currentChannel == null) { + return EMPTY; + } + final ArrayList<Epg> arrayList = EpgCache.CACHE.get(currentChannel + .getId()); + if (arrayList == null) { + return EMPTY; + } + return arrayList; + } + + @Override + protected void fillAdapter() { + + adapter.clear(); + + final ArrayList<Epg> cache = getCache(); + + if (cache.isEmpty()) { + return; + } + + sort(); + + final Calendar cal = Calendar.getInstance(); + int day = -1; + for (final Event e : cache) { + cal.setTime(e.getStart()); + final int eday = cal.get(Calendar.DAY_OF_YEAR); + if (eday != day) { + day = eday; + adapter.add(new EventListItem(new DateFormatter(cal) + .getDailyHeader())); + } + adapter.add(new EventListItem(e)); + } + + adapter.notifyDataSetChanged(); + + } + + /* + * (non-Javadoc) TODO this method also should be used in startEpgQuery on + * cache hit + * + * @see de.bjusystems.vdrmanager.gui.BaseEpgListActivity#finishedSuccess() + */ + @Override + protected boolean finishedSuccessImpl(final List<Epg> results) { + // adapter.clear(); + // CACHE.clear(); + + clearCache(); + + if (results.isEmpty()) { + return false; + } + + final Date now = new Date(); + + EpgCache.NEXT_REFRESH.put(currentChannel.getId(), FUTURE); + + Date nextForceCache = FUTURE; + + // Calendar cal = Calendar.getInstance(); + // int day = -1; + // sortItemsByTime(results); + final ArrayList<Epg> cache = new ArrayList<Epg>(); + for (final Epg e : results) { + cache.add(e); + // cal.setTime(e.getStart()); + // int eday = cal.get(Calendar.DAY_OF_YEAR); + // if (eday != day) { + // day = eday; + // adapter.add(new EventListItem(new DateFormatter(cal) + // .getDailyHeader())); + // } + // adapter.add(new EventListItem((Epg) e)); + if (e.getStop().before(nextForceCache) && e.getStop().after(now)) { + nextForceCache = e.getStop(); + } + } + + EpgCache.NEXT_REFRESH.put(currentChannel.getId(), nextForceCache); + EpgCache.CACHE.put(currentChannel.getId(), cache); + + fillAdapter(); + listView.setSelectionAfterHeaderView(); + return results.isEmpty() == false; + + // /////////////// + + // // get spinner value + // final EpgSearchTimeValue selection = (EpgSearchTimeValue) timeSpinner + // .getSelectedItem(); + // nextForceCache = FUTURE; + // cachedTime = selection.getValue(); + // Date now = new Date(); + // + // //adapter.add(new EventListItem(new DateFormatter(results.get(0) + // // .getStart()).getDailyHeader())); + // + // for (Event e : results) { + // CACHE.add(e); + // if (e.getStop().before(nextForceCache) && e.getStop().after(now)) { + // nextForceCache = e.getStop(); + // } + // } + // + + } + +// @Override +// protected int prepareDetailsViewData(final EventListItem item, int position) { +// final VdrManagerApp app = (VdrManagerApp) getApplication(); +// app.setCurrentEvent(item.getEvent()); +// ArrayList<Epg> cache = getCache(); +// app.setCurrentEpgList(cache); +// for (int i = 0; i < position; ++i) { +// if (cache.get(i) == item.getEvent()) { +// return i; +// } +// } +// +// return 0; +// } + + @Override + protected int getMainLayout() { + return R.layout.event_epg_list; + } + + @Override + protected void refresh() { + startEpgQuery(true); + } + + @Override + protected void retry() { + refresh(); + } + + @Override + protected String getWindowTitle() { + return getString(R.string.epg_by_channel); + } + + private void nextEvent() { + final int pos = channelSpinner.getSelectedItemPosition(); + if (pos + 1 >= channelSpinnerAdapter.getCount()) { + Toast.makeText(this, R.string.navigae_at_the_end, + Toast.LENGTH_SHORT).show(); + return; + } + channelSpinner.setSelection(pos + 1, true); + } + + private void prevEvent() { + final int pos = channelSpinner.getSelectedItemPosition(); + if (pos <= 0) { + say(R.string.navigae_at_the_start); + return; + } + channelSpinner.setSelection(pos - 1, true); + } + + @Override + public void onSwipe(final int direction) { + switch (direction) { + case SimpleGestureFilter.SWIPE_RIGHT: + prevEvent(); + break; + case SimpleGestureFilter.SWIPE_LEFT: + nextEvent(); + break; + } + } + + @Override + protected int getListNavigationIndex() { + return LIST_NAVIGATION_EPG_BY_CHANNEL; + } + + @Override + protected List<Epg> getCACHE() { + return getCache(); + } + + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.epg_event_list_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == R.id.epg_list_stream) { + Utils.stream(this, currentChannel); + return true; + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EventListItemHolder.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EventListItemHolder.java new file mode 100644 index 0000000..eaf70bb --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/EventListItemHolder.java @@ -0,0 +1,19 @@ +package de.bjusystems.vdrmanager.gui; + + +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +class EventListItemHolder { + public ImageView state; + public ImageView other; + public TextView title; + public TextView date; + public TextView time; + public TextView channel; + public TextView shortText; + public ProgressBar progress; + public TextView duration; + public TextView description; +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/PreferencesActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/PreferencesActivity.java new file mode 100644 index 0000000..e8ae690 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/PreferencesActivity.java @@ -0,0 +1,144 @@ +package de.bjusystems.vdrmanager.gui; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceChangeListener; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceManager; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.backup.BackupSettingsActivity; +import de.bjusystems.vdrmanager.data.Preferences; + +public class PreferencesActivity extends BasePreferencesActivity implements + OnSharedPreferenceChangeListener, OnPreferenceChangeListener, + OnPreferenceClickListener { +// +// Preference somePreference = findPreference(SOME_PREFERENCE_KEY); +// PreferenceScreen preferenceScreen = getPreferenceScreen(); +// preferenceScreen.removePreference(somePreference); +// +// you can later call: +// +// preferenceScreen.addPreference(somePreference); + private static final String TAG = "PreferencesActivity"; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + this.getPreferenceManager().setSharedPreferencesName( + Preferences.getPreferenceFile(this)); + this.addPreferencesFromResource(R.xml.preferences); + + + Preference backupPreference = findPreference(getString(R.string.settings_backup_key)); + backupPreference.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + Intent intent = new Intent(PreferencesActivity.this, BackupSettingsActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + return true; + } + }); + + updateChildPreferences(); + } + + // /** Return a properly configured SharedPreferences instance */ + public static SharedPreferences getSharedPreferences(Context context) { + return PreferenceManager.getDefaultSharedPreferences(context); + } + + @Override + protected void onPause() { + super.onPause(); + // Unregister the listener whenever a key changes + Preferences.getSharedPreferences(this) + .unregisterOnSharedPreferenceChangeListener(this); + } + + @Override + protected void onResume() { + super.onResume(); + // Set up a listener whenever a key changes + Preferences.getSharedPreferences(this) + .registerOnSharedPreferenceChangeListener(this); + + } + + + + private void updateChildPreferences() { + SharedPreferences sp = Preferences.getSharedPreferences(this); + + for (String key : sp.getAll().keySet()) { + Preference p = findPreference(key); + updateSummary(p); + } + + + } + + @Override + public void onBackPressed() { + //Preferences.getSharedPreferences(this) + //.registerOnSharedPreferenceChangeListener(this); + + // finish this activity + //Preferences.init(this); + // restart main activity because + // the buttons needs refreshing + Intent intent = new Intent(this, VdrManagerActivity.class); + int flags = Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TOP; + intent.setFlags(flags); + startActivity(intent); + finish(); + } + + public void onSharedPreferenceChanged(SharedPreferences arg0, String key) { + updateChildPreferences(); + Preference p = findPreference(key); + updateSummary(p); + //Preferences.reset(); + Preferences.init(this); + //Preferences.setLocale(getBaseContext()); + } + + public boolean onPreferenceChange(Preference arg0, Object arg1) { + return false; + } + + public boolean onPreferenceClick(Preference arg0) { + // if (arg0.getKey().equals(getString(R.string.wakeup_wol_mac_key))) { + // // if(arg0.) + // String host = Preferences.getPreferences().getSvdrpHost(); + // + // if(host == null || host.length() == 0){ + // return true; + // } + // + // try { + // Socket echoSocket = new Socket(host, 7); + // InetAddress ia = echoSocket.getInetAddress(); + // + // } catch (Exception ex){ + // Log.w(TAG,ex); + // } + // + // String mac = WakeOnLan.getMacFromArpCache(host); + // System.err.println("mac"); + // } + + return true; + } + + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/RecordingAdapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/RecordingAdapter.java new file mode 100644 index 0000000..b664276 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/RecordingAdapter.java @@ -0,0 +1,137 @@ +package de.bjusystems.vdrmanager.gui; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventFormatter; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.data.Recording; +import de.bjusystems.vdrmanager.data.RecordingListItem; + +class RecordingAdapter extends BaseEventAdapter<EventListItem> { + + protected final static int TYPE_FOLDER = 2; + + public RecordingAdapter(final Context context) { + super(context, R.layout.epg_event_item); + hideChannelName = false; + } + + @Override + protected EventFormatter getEventFormatter(Event event) { + return new EventFormatter(event, true); + } + + @Override + public int getViewTypeCount() { + return 3; + } + + @Override + protected boolean isHeader(EventListItem item) { + if (item instanceof RecordingListItem == false) { + return item.isHeader(); + } + + if (((RecordingListItem) item).isFolder()) { + return false; + } + + return item.isHeader(); + } + + @Override + public int getItemViewType(int position) { + + // get item + final RecordingListItem item = (RecordingListItem) getItem(position); + + if (item.isHeader()) { + return TYPE_HEADER; + } else if (item.isFolder()) { + return TYPE_FOLDER; + } + return TYPE_ITEM; + } + + class EventListItemFolderHolder { + public TextView folder; + public TextView count; + } + + protected EventListItemFolderHolder getFolderViewHolder(EventListItem item, + View view) { + EventListItemFolderHolder itemHolder = new EventListItemFolderHolder(); + itemHolder.folder = (TextView) view.findViewById(R.id.header_item); + itemHolder.count = (TextView) view.findViewById(R.id.count); + return itemHolder; + } + + @Override + public View getView(final int position, View convertView, + final ViewGroup parent) { + + // get item + final RecordingListItem item = (RecordingListItem) getItem(position); + + if (item.isFolder() == false) { + return super.getView(position, convertView, parent); + } + + EventListItemFolderHolder holder = null; + if (convertView == null + || (convertView != null && convertView.getTag() instanceof EventListItemFolderHolder) == false) { + convertView = inflater.inflate(R.layout.folder_item, null); + holder = getFolderViewHolder(item, convertView); + convertView.setTag(holder); + } else { + holder = (EventListItemFolderHolder) convertView.getTag(); + } + + holder.folder + .setText(Utils.highlight(item.folder.getName(), highlight)); + holder.count.setText(String.valueOf(item.folder.size())); + return convertView; + } + + @Override + public RecordingListItem getItem(int position) { + return (RecordingListItem) super.getItem(position); + } + + // + // protected void addSuper(RecordingListItem item) { + // super.addSuper(item); + // } + // + // protected void clearSuper() { + // super.clear(); + // } + protected void handleState(EventListItemHolder itemHolder, + EventListItem item) { + + Recording r = (Recording) item.getEvent(); + if (r.getTimerStopTime() != null) { + itemHolder.state.setImageResource(R.drawable.timer_recording); + } else { + itemHolder.state.setImageResource(R.drawable.timer_none); + itemHolder.other.setVisibility(View.GONE); + if (r.isNeww() == true) { + itemHolder.state.setImageResource(R.drawable.newrecording); + if (r.isCut()) { + itemHolder.other.setVisibility(View.VISIBLE); + itemHolder.other.setImageResource(R.drawable.schere); + } else { + itemHolder.other.setVisibility(View.GONE); + } + } else if (r.isCut()) { + itemHolder.state.setImageResource(R.drawable.schere); + } + } + + } + +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/RecordingDir.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/RecordingDir.java new file mode 100644 index 0000000..ce7b995 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/RecordingDir.java @@ -0,0 +1,71 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +import de.bjusystems.vdrmanager.data.Recording; + +public class RecordingDir { + + public RecordingDir() { + + } + + public String name; + + public RecordingDir parent; + + public TreeMap<String, RecordingDir> dirs = new TreeMap<String, RecordingDir>(); + + public List<Recording> recordings = new ArrayList<Recording>(); + + public String getName() { + return name; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[").append(name).append(" => {"); + String sep = ""; + for (RecordingDir e : dirs.values()) { + sb.append(sep).append(e.toString()); + sep = ", "; + } + + sb.append("}\n-").append("\n"); + + sb.append("{").append(""); + + sep = ""; + for (Recording r : recordings) { + sb.append(sep).append(r.toString()); + } + sb.append("}]"); + return sb.toString(); + } + + public void clear() { + for (RecordingDir dir : dirs.values()) { + dir.clear(); + } + recordings.clear(); + } + + public int size() { + int sum = 0; + for (RecordingDir d : dirs.values()) { + sum += d.size(); + } + sum += recordings.size(); + return sum; + } + + public String getPath(){ + if(parent == null){ + return "/"; + } + return parent.getPath() + name + "/"; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/RecordingListActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/RecordingListActivity.java new file mode 100644 index 0000000..50f0971 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/RecordingListActivity.java @@ -0,0 +1,568 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Stack; + +import android.os.Bundle; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ListView; +import android.widget.ProgressBar; +import android.widget.TextView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventFormatter; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Recording; +import de.bjusystems.vdrmanager.data.RecordingListItem; +import de.bjusystems.vdrmanager.tasks.DeleteRecordingTask; +import de.bjusystems.vdrmanager.utils.date.DateFormatter; +import de.bjusystems.vdrmanager.utils.svdrp.RecordingClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpEvent; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpStartListener; + +/** + * This class is used for showing what's current running on all channels + * + * @author bju + */ +public class RecordingListActivity extends BaseEventListActivity<Recording> + implements OnItemLongClickListener, SvdrpStartListener { + + // RecordingClient recordingClient; + + // public static final int MENU_GROUP_CHANNEL = 2; + + public static final int ASC = 0; + + public static final int DESC = 1; + + // protected static ArrayList<Recording> CACHE = new ArrayList<Recording>(); + + // private static Map<String, List<Recording>> CACHE = new TreeMap<String, + // List<Recording>>(); + + // public static final Map<String, Set<String>> FOLDERS = new + // TreeMap<String, Set<String>>(); + + private RecordingDir currentFolder; // Recording.ROOT_FOLDER; + + private final int ASC_DESC = ASC; + + private static final List<Recording> EMPTY = new ArrayList<Recording>(0); + + private final Stack<String> stack = new Stack<String>(); + + private TextView folderInfo; + + private TextView currentCount; + + private TextView driverInfo; + + private View driverInfoContainer; + + private ProgressBar drive_info_pb; + + private int totalMB = -1; + + private int freeMB = -1; + + private int percent = -1; + + private RecordingDir ROOT = new RecordingDir(); + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + adapter = new RecordingAdapter(this); + currentFolder = ROOT; + // attach adapter to ListView + listView = (ListView) findViewById(R.id.recording_list); + folderInfo = (TextView) findViewById(R.id.folder_info); + driverInfoContainer = findViewById(R.id.driver_info_container); + driverInfo = (TextView) driverInfoContainer + .findViewById(R.id.drive_info); + drive_info_pb = (ProgressBar) driverInfoContainer + .findViewById(R.id.drive_info_pb); + currentCount = (TextView) findViewById(R.id.current_count); + listView.setAdapter(adapter); + + // set click listener + listView.setOnItemLongClickListener(this); + // register EPG item click + listView.setOnItemClickListener(this); + // context menu wanted + registerForContextMenu(listView); + listView.setFastScrollEnabled(true); + listView.setTextFilterEnabled(true); + // start query + startRecordingQuery(); + } + + @Override + protected int getAvailableSortByEntries() { + return R.array.recordings_group_by; + }; + + // AlertDialog groupByDialog = null; + + // @Override + // public boolean onOptionsItemSelected( + // final com.actionbarsherlock.view.MenuItem item) { + // + // switch (item.getItemId()) { + // case R.id.menu_groupby: + // // case MENU_PROVIDER: + // // case MENU_NAME: + // if (groupByDialog == null) { + // groupByDialog = new AlertDialog.Builder(this) + // .setTitle(R.string.menu_groupby) + // .setIcon(android.R.drawable.ic_menu_sort_alphabetically) + // .setSingleChoiceItems(getAvailableGroupByEntries(), + // groupBy, new DialogInterface.OnClickListener() { + // public void onClick(DialogInterface dialog, + // int which) { + // if (groupBy == which) { + // ASC_DESC = ASC_DESC == ASC ? DESC + // : ASC; + // } else { + // groupBy = which; + // ASC_DESC = ASC; + // } + // // fillAdapter(); + // groupByDialog.dismiss(); + // say("Comming soon..."); + // } + // }).create(); + // } + // + // groupByDialog.show(); + // + // return true; + // default: + // return super.onOptionsItemSelected(item); + // } + // } + + @Override + public void onItemClick(final AdapterView<?> parent, final View view, + final int position, final long id) { + + final RecordingListItem item = (RecordingListItem) adapter + .getItem(position); + if (item.isFolder()) { + currentFolder = item.folder; + // if (currentFolder.equals(Recording.ROOT_FOLDER)) { + // currentFolder = item.folder; + // } else { + // currentFolder = currentFolder + Recording.FOLDERDELIMCHAR + // + item.folder; + // } + // stack.push(currentFolder); + fillAdapter(); + } else { + super.onItemClick(parent, view, position, id); + } + } + + private void updateCurrentFolderInfo(int size) { + folderInfo.setText(currentFolder.getPath()); + currentCount.setText(String.valueOf(size)); + } + + /* + * (non-Javadoc) + * + * @see + * de.bjusystems.vdrmanager.gui.BaseActivity#onCreateOptionsMenu(android + * .view.Menu) + */ + @Override + public boolean onCreateOptionsMenu( + final Menu menu) { + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.recording_list_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + protected void onPause() { + super.onPause(); + } + +// @Override +// protected int prepareDetailsViewData(final EventListItem event, int position) { +// getApp().setCurrentEvent(event.getEvent()); +// List<Recording> cachEget = CACHEget(currentFolder); +// getApp().setCurrentEpgList(cachEget); +// +// for(int i = 0; i < position; ++i){ +// if(cachEget.get(i) == event.getEvent()){ +// return i; +// } +// } +// +// return 0; +// } + + @Override + public void onCreateContextMenu(final ContextMenu menu, final View v, + final ContextMenuInfo menuInfo) { + + final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + final EventListItem item = adapter.getItem(info.position); + if (item.isHeader()) { + return; + } + + if (v.getId() == R.id.recording_list) { + final MenuInflater inflater = getMenuInflater(); + // set menu title + final EventFormatter formatter = new EventFormatter(item); + menu.setHeaderTitle(formatter.getTitle()); + + inflater.inflate(R.menu.recording_list_item_menu, menu); + if (Preferences.get().isEnableRecStream() == false) { + menu.removeItem(R.id.recording_item_menu_stream); + } + + } + + super.onCreateContextMenu(menu, v, menuInfo); + // + // http://projects.vdr-developer.org/issues/863 + // if (Utils.isLive(item)) { + menu.removeItem(R.id.epg_item_menu_live_tv); + // } + } + + @Override + public boolean onContextItemSelected(final MenuItem item) { + + final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item + .getMenuInfo(); + final EventListItem event = adapter.getItem(info.position); + final Recording rec = (Recording) event.getEvent(); + switch (item.getItemId()) { + case R.id.recording_item_menu_delete: { + final DeleteRecordingTask drt = new DeleteRecordingTask(this, rec) { + @Override + public void finished(final SvdrpEvent event) { + if (event == SvdrpEvent.FINISHED_SUCCESS) { + backupViewSelection(); + refresh(); + } + } + }; + drt.start(); + break; + } + case R.id.recording_item_menu_stream: { + Utils.streamRecording(this, rec); + // say("Sorry, not yet. It would be. File -> " + rec.getFileName()); + break; + } + + default: + return super.onContextItemSelected(item); + } + return true; + } + + private void startRecordingQuery() { + + if (checkInternetConnection() == false) { + return; + } + + // get timer client + final RecordingClient recordingClient = new RecordingClient( + getCertificateProblemDialog()); + + recordingClient.addStartListener(this); + + // create backgound task + final SvdrpAsyncTask<Recording, SvdrpClient<Recording>> task = new SvdrpAsyncTask<Recording, SvdrpClient<Recording>>( + recordingClient); + + // create progress dialog + + addListener(task); + + // start task + task.run(); + } + + @Override + protected void retry() { + startRecordingQuery(); + } + + @Override + protected void refresh() { + startRecordingQuery(); + } + + @Override + protected int getMainLayout() { + return R.layout.recording_list; + } + + @Override + protected String getWindowTitle() { + return getString(R.string.action_menu_recordings); + } + + protected void sort() { + /* */ + switch (sortBy) { + case MENU_GROUP_DEFAULT: { + sortItemsByTime(CACHEget(currentFolder), true); + break; + } + case MENU_GROUP_ALPHABET: { + Collections.sort(CACHEget(currentFolder), new TitleComparator()); + break; + } + // case MENU_GROUP_CHANNEL: { + // sortItemsByChannel(results); + // } + } + } + + private List<Recording> CACHEget(RecordingDir currentFolder) { + return currentFolder.recordings; + } + + + @Override + protected void fillAdapter() { + + adapter.clear(); + + sort(); + + final Calendar cal = Calendar.getInstance(); + int day = -1; + + final Collection<RecordingDir> folders = currentFolder.dirs.values(); + for (final RecordingDir d : folders) { + final RecordingListItem recordingListItem = new RecordingListItem( + d.name); + recordingListItem.folder = d; + // final String sf = currentFolder.length() > 0 ? currentFolder + // + Recording.FOLDERDELIMCHAR + f : f; + // final List<Recording> list2 = CACHE.get(sf); + // if (list2 != null) { + // recordingListItem.getCcount = getCountInFolder(currentFolder, f); + // } + adapter.add(recordingListItem); + } + + updateCurrentFolderInfo(currentFolder.size()); + + for (final Event rec : CACHEget(currentFolder)) { + cal.setTime(rec.getStart()); + final int eday = cal.get(Calendar.DAY_OF_YEAR); + if (eday != day) { + day = eday; + adapter.add(new RecordingListItem(new DateFormatter(cal) + .getDailyHeader())); + } + adapter.add(new RecordingListItem((Recording) rec)); + } + adapter.notifyDataSetChanged(); + } + + @Override + public void onBackPressed() { + if (currentFolder.parent == null) { + super.onBackPressed(); + return; + } + currentFolder = currentFolder.parent; + fillAdapter(); + } + +// private List<Recording> readDummy() { +// +// List<Recording> list = new ArrayList<Recording>(); +// try { +// LineNumberReader r = new LineNumberReader(new FileReader(new File( +// "/sdcard/vdrmanager.txt"))); +// +// String line; +// +// while ((line = r.readLine()) != null) { +// Recording rec = null; +// try { +// rec = new Recording(line); +// } catch (Exception ex) { +// ex.printStackTrace(); +// } +// list.add(rec); +// } +// +// } catch (Exception ex) { +// ex.printStackTrace(); +// } +// +// return list; +// +// } + + @Override + protected boolean finishedSuccessImpl(final List<Recording> results) { + + // results.clear(); + // results.addAll(readDummy()); + + clearCache(); + + for (final Recording r : results) { + final String folder = r.getFolder(); + + if (folder.length() == 0) { + ROOT.recordings.add(r); + } else { + final String[] split = folder.split(Recording.FOLDERDELIMCHAR); + + RecordingDir parent = ROOT; + RecordingDir dir = null; // "a~b~c"; + for (int i = 0; i < split.length; ++i) { + String dn = split[i]; + + dir = parent.dirs.get(dn); + if(dir == null) { + dir = new RecordingDir(); + dir.parent = parent; + dir.name = dn; + parent.dirs.put(dn, dir); + } + parent = dir; + } + dir.recordings.add(r); + + // StringBuilder sb = new StringBuilder(); + // String sep = ""; + // for(int i = 0; i < split.length - 1; ++i){ + // sb.append(sep).append(split[i]); + // sep = Recording.FOLDERDELIMCHAR; + // } + // key = folder.subSequence(0, + // folder.length() - (value.length() + 1)).toString(); + + // Set<String> list = FOLDERS.get(key); + // if (list == null) { + // list = new TreeSet<String>(new Comparator<String>() { + // @Override + // public int compare(final String lhs, final String rhs) { + // return lhs.compareToIgnoreCase(rhs); + // } + // }); + // FOLDERS.put(key, list); + // } + // + // list.add(value); + // + // if (key.equals(Recording.ROOT_FOLDER) == false) { + // Set<String> set = FOLDERS.get(Recording.ROOT_FOLDER); + // if (set == null) { + // set = new HashSet<String>(); + // FOLDERS.put(key, set); + // } + // set.add(key); + // } + + // a b + // a + // c + // a~b > k + + } + // List<Recording> list = CACHE.get(folder); + // if (list == null) { + // list = new ArrayList<Recording>(); + // CACHE.put(folder, list); + // } + // list.add(r); + } + + pushResultCountToTitle(); + fillAdapter(); + return adapter.isEmpty() == false; + } + + @Override + public boolean onItemLongClick(final AdapterView<?> arg0, final View arg1, + final int arg2, final long arg3) { + + return false; + } + + @Override + protected int getListNavigationIndex() { + return LIST_NAVIGATION_RECORDINGS; + } + + @Override + public void clearCache() { + ROOT.clear(); + // CACHE.clear(); + // FOLDERS.clear(); + } + + @Override + protected List<Recording> getCACHE() { + + return currentFolder.recordings; + } + + @Override + public void start(final String meta) { + if (meta == null) { + return; + } + try { + + runOnUiThread(new Runnable() { + public void run() { + + // stuff that updates ui + + String[] split = meta.split(":"); + if (split.length != 3) { + Log.w(TAG, "Recoring list meta is wrong"); + return; + } + + totalMB = Integer.valueOf(split[0]); + freeMB = Integer.valueOf(split[1]); + percent = Integer.valueOf(split[2]); + driverInfoContainer.setVisibility(View.VISIBLE); + driverInfo.setText(getString(R.string.drive_info, + (freeMB) / 1024, totalMB / 1024, 100 - percent)); + drive_info_pb.setProgress(percent); + } + }); + } catch (Exception ex) { + Log.w(TAG, ex.getMessage(), ex); + } + + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/SimpleGestureFilter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/SimpleGestureFilter.java new file mode 100644 index 0000000..2afe23a --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/SimpleGestureFilter.java @@ -0,0 +1,170 @@ +package de.bjusystems.vdrmanager.gui; + +import android.app.Activity; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; + +public class SimpleGestureFilter extends SimpleOnGestureListener{ + + public final static int SWIPE_UP = 1; + public final static int SWIPE_DOWN = 2; + public final static int SWIPE_LEFT = 3; + public final static int SWIPE_RIGHT = 4; + + public final static int MODE_TRANSPARENT = 0; + public final static int MODE_SOLID = 1; + public final static int MODE_DYNAMIC = 2; + + private final static int ACTION_FAKE = -13; //just an unlikely number + private int swipe_Min_Distance = 100; + private int swipe_Max_Distance = 350; + private int swipe_Min_Velocity = 100; + + private int mode = MODE_DYNAMIC; + private boolean running = true; + private boolean tapIndicator = false; + + private Activity context; + private GestureDetector detector; + private SimpleGestureListener listener; + + + public SimpleGestureFilter(Activity context, SimpleGestureListener sgl) { + + this.context = context; + this.detector = new GestureDetector(context, this); + this.listener = sgl; + } + + public void onTouchEvent(MotionEvent event){ + + if(!this.running) + return; + + boolean result = this.detector.onTouchEvent(event); + + if(this.mode == MODE_SOLID) + event.setAction(MotionEvent.ACTION_CANCEL); + else if (this.mode == MODE_DYNAMIC) { + + if(event.getAction() == ACTION_FAKE) + event.setAction(MotionEvent.ACTION_UP); + else if (result) + event.setAction(MotionEvent.ACTION_CANCEL); + else if(this.tapIndicator){ + event.setAction(MotionEvent.ACTION_DOWN); + this.tapIndicator = false; + } + + } + //else just do nothing, it's Transparent + } + + public void setMode(int m){ + this.mode = m; + } + + public int getMode(){ + return this.mode; + } + + public void setEnabled(boolean status){ + this.running = status; + } + + public void setSwipeMaxDistance(int distance){ + this.swipe_Max_Distance = distance; + } + + public void setSwipeMinDistance(int distance){ + this.swipe_Min_Distance = distance; + } + + public void setSwipeMinVelocity(int distance){ + this.swipe_Min_Velocity = distance; + } + + public int getSwipeMaxDistance(){ + return this.swipe_Max_Distance; + } + + public int getSwipeMinDistance(){ + return this.swipe_Min_Distance; + } + + public int getSwipeMinVelocity(){ + return this.swipe_Min_Velocity; + } + + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + + final float xDistance = Math.abs(e1.getX() - e2.getX()); + final float yDistance = Math.abs(e1.getY() - e2.getY()); + + if(xDistance > this.swipe_Max_Distance || yDistance > this.swipe_Max_Distance) + return false; + + velocityX = Math.abs(velocityX); + velocityY = Math.abs(velocityY); + boolean result = false; + + if(velocityX > this.swipe_Min_Velocity && xDistance > this.swipe_Min_Distance){ + if(e1.getX() > e2.getX()) // right to left + this.listener.onSwipe(SWIPE_LEFT); + else + this.listener.onSwipe(SWIPE_RIGHT); + + result = true; + } + /* + else if(velocityY > this.swipe_Min_Velocity && yDistance > this.swipe_Min_Distance){ + if(e1.getY() > e2.getY()) // bottom to up + this.listener.onSwipe(SWIPE_UP); + else + this.listener.onSwipe(SWIPE_DOWN); + + result = true; + } +*/ + return result; + } + + @Override + public boolean onSingleTapUp(MotionEvent e) { + this.tapIndicator = true; + return false; + } + + @Override + public boolean onDoubleTap(MotionEvent arg0) { + this.listener.onDoubleTap();; + return true; + } + + @Override + public boolean onDoubleTapEvent(MotionEvent arg0) { + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent arg0) { + + if(this.mode == MODE_DYNAMIC){ // we owe an ACTION_UP, so we fake an + arg0.setAction(ACTION_FAKE); //action which will be converted to an ACTION_UP later. + this.context.dispatchTouchEvent(arg0); + } + + return false; + } + + + static interface SimpleGestureListener{ + void onSwipe(int direction); + void onDoubleTap(); + } + + }
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/SvdrpProgressDialog.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/SvdrpProgressDialog.java new file mode 100644 index 0000000..74cd502 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/SvdrpProgressDialog.java @@ -0,0 +1,69 @@ +package de.bjusystems.vdrmanager.gui; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpEvent; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpException; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpExceptionListener; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpListener; + +public class SvdrpProgressDialog<T> extends ProgressDialog implements + SvdrpExceptionListener, SvdrpListener, DialogInterface.OnCancelListener { + + ProgressDialog progress; + + SvdrpClient<? extends Object> client; + + public SvdrpProgressDialog(final Context context, + final SvdrpClient<T> client) { + super(context); + + this.client = client; + progress = new ProgressDialog(context); + progress.setOnCancelListener(this); + progress.setCancelable(true); + progress.setCanceledOnTouchOutside(false); + } + + public void svdrpEvent(final SvdrpEvent sevent) { + switch (sevent) { + case ABORTED: + case CONNECTION_TIMEOUT: + case CONNECT_ERROR: + case ERROR: + case LOGIN_ERROR: + case FINISHED_ABNORMALY: + case FINISHED_SUCCESS: + case CACHE_HIT: + progress.dismiss(); + break; + case DISCONNECTED: + break; + } + } + + public void svdrpException(final SvdrpException exception) { + + } + + private void abort() { + client.abort(); + dismiss(); + } + + public void dismiss() { + progress.dismiss(); + } + + public void onCancel(DialogInterface dialog) { + abort(); + } + + @Override + public void svdrpEvent(SvdrpEvent event, Throwable t) { + progress.dismiss(); + Utils.say(getContext(), t.getLocalizedMessage()); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimeEpgListActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimeEpgListActivity.java new file mode 100644 index 0000000..c9c6a57 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimeEpgListActivity.java @@ -0,0 +1,428 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; + +import android.app.TimePickerDialog; +import android.app.TimePickerDialog.OnTimeSetListener; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TimePicker; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.data.Epg; +import de.bjusystems.vdrmanager.data.EpgSearchTimeValue; +import de.bjusystems.vdrmanager.data.EpgSearchTimeValues; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.utils.date.DateFormatter; +import de.bjusystems.vdrmanager.utils.svdrp.EpgClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; + +/** + * This class is used for showing what's current running on all channels + * + * @author bju + */ +public class TimeEpgListActivity extends BaseTimerEditActivity<Epg> implements + OnItemClickListener, OnItemSelectedListener, OnTimeSetListener { + + protected Spinner timeSpinner; + + protected View switcher; + + protected View clock; + + ArrayAdapter<EpgSearchTimeValue> timeSpinnerAdapter; + + protected static Date nextForceCache = null; + + private static String cachedTime = null; + + int selectedIndex = 0; + + protected static ArrayList<Epg> CACHE = new ArrayList<Epg>(); + + @Override + public int getProgressTextId() { + return R.string.progress_whatson_loading; + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // create adapter for time spinner + timeSpinnerAdapter = new ArrayAdapter<EpgSearchTimeValue>(this, + android.R.layout.simple_spinner_item); + timeSpinnerAdapter + .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + timeSpinner = (Spinner) findViewById(R.id.epg_list_time_spinner); + timeSpinner.setAdapter(timeSpinnerAdapter); + + switcher = findViewById(R.id.switch_epg_view); + switcher.setOnClickListener(this); + + clock = findViewById(R.id.epg_list_times); + clock.setOnClickListener(this); + + // update gui + adapter = new TimeEventAdapter(this); + // searchLabel.setVisibility(View.GONE); + timeSpinner.setOnItemSelectedListener(this); + + fillTimeSpinnerValues(); + + // Create adapter for EPG list + listView = (ListView) findViewById(R.id.whatson_list); + listView.setFastScrollEnabled(true); + listView.setTextFilterEnabled(true); + + listView.setAdapter(adapter); + registerForContextMenu(listView); + + // register EPG item click + listView.setOnItemClickListener(this); + + } + + private void fillTimeSpinnerValues() { + final EpgSearchTimeValues values = new EpgSearchTimeValues(this); + timeSpinnerAdapter.clear(); + for (final EpgSearchTimeValue value : values.getValues()) { + timeSpinnerAdapter.add(value); + } + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + protected void onPause() { + super.onPause(); + + } + + @Override + public void onClick(final View view) { + if (view == switcher) { + final Intent intent = new Intent(); + intent.setClass(this, EventEpgListActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT + | Intent.FLAG_ACTIVITY_SINGLE_TOP); + startActivity(intent); + finish(); + } else if (view == clock) { + final Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT + | Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.setClass(this, EpgSearchTimesListActivity.class); + startActivity(intent); + } else { + super.onClick(view); + } + } + + @Override + public void onTimeSet(final TimePicker view, final int hourOfDay, + final int minute) { + final String tm = String.format("%02d:%02d", hourOfDay, minute); + + // timeSpinnerAdapter.add(time); + final EpgSearchTimeValues values = new EpgSearchTimeValues(this); + final List<EpgSearchTimeValue> vs = values.getValues(); + final EpgSearchTimeValue time = new EpgSearchTimeValue(3, tm); + vs.add(vs.size() - 1, time); + timeSpinnerAdapter.clear(); + int select = -1; + int counter = 0; + for (final EpgSearchTimeValue value : vs) { + timeSpinnerAdapter.add(value); + if (select == -1 && value.getText().equals(tm)) { + select = counter; + } + counter++; + } + timeSpinner.setSelection(select); + setTitle(resolveWindowTitle()); + // update search + startEpgQuery(time.getValue(), false); + } + + private String resolveWindowTitle() { + if (timeSpinner == null) { + return getString(R.string.epg_by_time); + } + final EpgSearchTimeValue v = (EpgSearchTimeValue) timeSpinner + .getSelectedItem(); + if (v == null) { + return getString(R.string.epg_by_time); + } + return getString(R.string.epg_by_time_args, v.getText()); + } + + @Override + public void onItemSelected(final AdapterView<?> parent, final View view, + final int position, final long id) { + + // get spinner value + final EpgSearchTimeValue selection = (EpgSearchTimeValue) timeSpinner + .getSelectedItem(); + + if (selection.getValue().equals("adhoc")) { + final Calendar cal = Calendar.getInstance(TimeZone.getDefault()); + // show time selection + final TimePickerDialog dialog = new TimePickerDialog(this, this, + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), + Preferences.get().isUse24hFormat()); + // dialog.setOnDismissListener(new OnDismissListener() { + + // @Override + // public void onDismiss(DialogInterface dialog) { + + // } + // }); + + dialog.show(); + } else { + // update search + setTitle(getString(R.string.epg_by_time_args, selection.getText())); + startEpgQuery(selection.getValue(), false); + } + } + + @Override + public void onNothingSelected(final AdapterView<?> arg0) { + // startTimeEpgQuery(((EpgTimeSpinnerValue)timeSpinner.getAdapter().getItem(0)).getValue()); + } + + @Override + public void clearCache() { + super.clearCache(); + cachedTime = null; + } + + private boolean useCache(final String time) { + + if (cachedTime == null) { + return false; + } + + if (cachedTime.equals(time) == false) { + return false; + } + + if (nextForceCache == null) { + return false; + } + final Date now = new Date(); + if (nextForceCache.before(now)) { + return false; + } + return true; + } + + private void startEpgQuery(final String time, final boolean force) { + + if (useCache(time) && !force) { + fillAdapter(); + return; + } + + if (checkInternetConnection() == false) { + return; + } + + final EpgClient epgClient = new EpgClient(time, + getCertificateProblemDialog()); + + // remove old listeners + // epgClient.clearSvdrpListener(); + + // create background task + final SvdrpAsyncTask<Epg, SvdrpClient<Epg>> task = new SvdrpAsyncTask<Epg, SvdrpClient<Epg>>( + epgClient); + + // create progress + addListener(task); + + // start task + task.run(); + } + + @Override + protected synchronized void fillAdapter() { + + adapter.clear(); + + if (CACHE.isEmpty()) { + return; + } + + sort(); + listView.setFastScrollEnabled(false); + adapter.add(new EventListItem( + new DateFormatter(CACHE.get(0).getStart()).getDailyHeader())); + + for (final Event e : CACHE) { + adapter.add(new EventListItem(e)); + } + adapter.notifyDataSetChanged(); + listView.setFastScrollEnabled(true); + } + + void sort() { + if (sortBy == BaseEventListActivity.MENU_GROUP_ALPHABET) { + Collections.sort(CACHE, new TitleComparator()); + } else if (sortBy == BaseEventListActivity.MENU_GROUP_DEFAULT) { + Collections.sort(CACHE, new ChannelComparator()); + } + } + + @Override + protected int getAvailableSortByEntries() { + return R.array.epg_sort_by_channels_alpha; + } + + @Override + protected String getViewID() { + return TimeEpgListActivity.class.getSimpleName(); + } + + @Override + protected boolean finishedSuccessImpl(final List<Epg> results) { + clearCache(); + + if (results.isEmpty()) { + return false; + } + + // get spinner value + final EpgSearchTimeValue selection = (EpgSearchTimeValue) timeSpinner + .getSelectedItem(); + nextForceCache = FUTURE; + cachedTime = selection.getValue(); + final Date now = new Date(); + + // adapter.add(new EventListItem(new DateFormatter(results.get(0) + // .getStart()).getDailyHeader())); + + for (final Epg e : results) { + CACHE.add(e); + if (e.getStop().before(nextForceCache) && e.getStop().after(now)) { + nextForceCache = e.getStop(); + } + } + fillAdapter(); + pushResultCountToTitle(); + listView.setSelectionAfterHeaderView(); + return results.isEmpty() == false; + + } + +// @Override +// protected int prepareDetailsViewData(final EventListItem item, int position) { +// final VdrManagerApp app = (VdrManagerApp) getApplication(); +// // remember event for details view and timer things +// app.setCurrentEvent(item.getEvent()); +// ArrayList<EventListItem> current = new ArrayList<EventListItem>(); +// for (int i = 0; i < adapter.getCount(); ++i) { +// current.add(adapter.getItem(i)); +// } +// app.setCurrentEpgList(current); +// for (int i = 0; i < position; ++i) { +// if (current.get(i) == item.getEvent()) { +// return i; +// } +// } +// +// return 0; +// } + + @Override + protected int getMainLayout() { + return R.layout.time_epg_list; + } + + @Override + protected void refresh() { + // get spi + final EpgSearchTimeValue selection = (EpgSearchTimeValue) timeSpinner + .getSelectedItem(); + // update search + startEpgQuery(selection.getValue(), true); + } + + @Override + protected void retry() { + refresh(); + } + + @Override + protected String getWindowTitle() { + return resolveWindowTitle(); + } + + private void nextEvent() { + final int pos = timeSpinner.getSelectedItemPosition(); + if (pos + 1 >= timeSpinnerAdapter.getCount()) { + say(R.string.navigae_at_the_end); + return; + } + timeSpinner.setSelection(pos + 1, true); + } + + private void prevEvent() { + final int pos = timeSpinner.getSelectedItemPosition(); + if (pos <= 0) { + say(R.string.navigae_at_the_start); + return; + } + timeSpinner.setSelection(pos - 1, true); + } + + @Override + public void onSwipe(final int direction) { + switch (direction) { + case SimpleGestureFilter.SWIPE_RIGHT: + prevEvent(); + break; + case SimpleGestureFilter.SWIPE_LEFT: + nextEvent(); + break; + } + } + + @Override + protected int getListNavigationIndex() { + return LIST_NAVIGATION_EPG_BY_TIME; + } + + @Override + protected List<Epg> getCACHE() { + return CACHE; + } + + @Override + protected void timerModified(final Timer timer) { + clearCache(); + super.timerModified(timer); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimeEventAdapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimeEventAdapter.java new file mode 100644 index 0000000..53bc690 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimeEventAdapter.java @@ -0,0 +1,19 @@ +package de.bjusystems.vdrmanager.gui; + +import android.content.Context; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventFormatter; +import de.bjusystems.vdrmanager.data.EventListItem; + +public class TimeEventAdapter extends BaseEventAdapter<EventListItem> { + + public TimeEventAdapter(final Context context) { + super(context, R.layout.epg_event_item); + } + + @Override + protected EventFormatter getEventFormatter(Event event) { + return new EventFormatter(event, true); + } +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimerDetailsActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimerDetailsActivity.java new file mode 100644 index 0000000..c6ce4a6 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimerDetailsActivity.java @@ -0,0 +1,578 @@ +package de.bjusystems.vdrmanager.gui; + +import java.text.DateFormatSymbols; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.DatePickerDialog; +import android.app.DatePickerDialog.OnDateSetListener; +import android.app.TimePickerDialog; +import android.app.TimePickerDialog.OnTimeSetListener; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.DatePicker; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.TimePicker; +import android.widget.Toast; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.Intents; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.data.EpgCache; +import de.bjusystems.vdrmanager.data.EventFormatter; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.tasks.CreateTimerTask; +import de.bjusystems.vdrmanager.tasks.ModifyTimerTask; +import de.bjusystems.vdrmanager.utils.date.DateFormatter; +import de.bjusystems.vdrmanager.utils.svdrp.SetTimerClient.TimerOperation; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpEvent; + +public class TimerDetailsActivity extends Activity implements OnClickListener, + OnDateSetListener, OnTimeSetListener { + + public static final int REQUEST_CODE_TIMER_MODIFIED = 34; + + public static final int REQUEST_CODE_TIMER_EDIT = 35; + + public static final int REQUEST_CODE_TIMER_ADD = 36; + + private CharSequence prevStart; + + private CharSequence prevEnd; + + private CharSequence prevDate; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + View view = getLayoutInflater().inflate(R.layout.timer_detail, null); + tView = new EditTimerViewHolder(); + tView.view = view; + tView.title = (TextView) view.findViewById(R.id.timer_detail_title); + tView.channel = (TextView) view.findViewById(R.id.timer_detail_channel); + tView.dateField = (Button) view.findViewById(R.id.timer_detail_day); + tView.startField = (Button) view.findViewById(R.id.timer_detail_start); + tView.endField = (Button) view.findViewById(R.id.timer_detail_end); + tView.saveButton = (Button) view.findViewById(R.id.timer_details_save); + tView.modifyButton = (Button) view + .findViewById(R.id.timer_details_modify); + + tView.repeat = (Button) view.findViewById(R.id.timer_detail_repeat); + + tView.vps = (CheckBox) view.findViewById(R.id.timer_detail_vps); + + tView.priority = (EditText) view + .findViewById(R.id.timer_detail_priority); + + tView.lifecycle = (EditText) view + .findViewById(R.id.timer_detail_lifetime); + + view.findViewById(R.id.timer_details_cancel).setOnClickListener(this); + tView.dateField.setOnClickListener(this); + tView.startField.setOnClickListener(this); + tView.endField.setOnClickListener(this); + tView.saveButton.setOnClickListener(this); + tView.modifyButton.setOnClickListener(this); + tView.repeat.setOnClickListener(this); + setContentView(view); + timer = getApp().getCurrentTimer().copy(); + original = getApp().getCurrentTimer().copy(); + + int op = getIntent().getExtras().getInt(Intents.TIMER_OP); + switch (op) { + case Intents.ADD_TIMER: + setTitle(R.string.timer_details_add_title); + add(); + break; + case Intents.EDIT_TIMER: + setTitle(R.string.timer_details_modify_title); + modify(); + break; + + default: + finish(); + } + + if (timer.isVps() == false && timer.hasVPS() == false) { + findViewById(R.id.timer_block).setVisibility(View.GONE); + } else { + tView.vps + .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, + boolean isChecked) { + if (isChecked == true) { + vpsChecked(false); + } else { + vpsUnchecked(); + } + } + }); + } + } + + private void vpsChecked(boolean initial) { + + if (initial == false) { + prevStart = tView.startField.getText(); + prevEnd = tView.endField.getText(); + prevDate = tView.dateField.getText(); + } + + DateFormatter formatter = new DateFormatter(original.getStart()); + String date = formatter.getDateString(); + tView.startField.setEnabled(false); + tView.startField.setText(formatter.getTimeString()); + timer.setStart(original.getStart()); + + DateFormatter stopF = new DateFormatter(original.getStop()); + + tView.endField.setEnabled(false); + tView.endField.setText(stopF.getTimeString()); + timer.setStop(original.getStop()); + + tView.dateField.setEnabled(false); + tView.dateField.setText(date); + + } + + private void vpsUnchecked() { + if (prevStart != null) { + tView.startField.setText(prevStart); + } + tView.startField.setEnabled(true); + + if (prevEnd != null) { + tView.endField.setText(prevEnd); + } + tView.endField.setEnabled(true); + + if (prevDate != null) { + tView.dateField.setText(prevDate); + } + tView.dateField.setEnabled(true); + } + + public class EditTimerViewHolder { + View view; + TextView title; + TextView channel; + Button dateField; + Button startField; + Button endField; + Button saveButton; + Button modifyButton; + CheckBox vps; + Button repeat; + EditText priority; + EditText lifecycle; + } + + EditTimerViewHolder tView = null; + + boolean editStart; + + // SetTimerClient setTimerClient; + + Timer timer; + + Timer original; + + private void updateDates(Date start, Date stop) { + DateFormatter startF = new DateFormatter(start); + DateFormatter endF = new DateFormatter(stop); + tView.startField.setText(startF.getTimeString()); + tView.endField.setText(endF.getTimeString()); + tView.dateField.setText(startF.getDateString()); + } + + private void updateDisplay(TimerOperation op) { + updateDisplay(); + + switch (op) { + case CREATE: + tView.modifyButton.setVisibility(View.GONE); + tView.saveButton.setVisibility(View.VISIBLE); + tView.saveButton.setText(R.string.timer_details_create_title); + Preferences prefs = Preferences.get(); + tView.priority.setText(String.valueOf(prefs + .getTimerDefaultPriority())); + tView.lifecycle.setText(String.valueOf(prefs + .getTimerDefaultLifetime())); + + Date start = new Date(timer.getStart().getTime() + - prefs.getTimerPreMargin() * 60000); + timer.setStart(start); + + Date end = new Date(timer.getStop().getTime() + + prefs.getTimerPostMargin() * 60000); + timer.setStop(end); + + updateDates(start, end); + break; + case MODIFY: + tView.saveButton.setVisibility(View.GONE); + tView.modifyButton.setVisibility(View.VISIBLE); + tView.saveButton.setText(R.string.timer_details_save_title); + tView.priority.setText(String.valueOf(timer.getPriority())); + tView.lifecycle.setText(String.valueOf(timer.getLifetime())); + if (timer.isVps()) { + vpsChecked(true); + } else { + updateDates(timer.getStart(), timer.getStop()); + } + + break; + default: + throw new RuntimeException("Unknown Operation: " + op); + } + + } + + private void updateDisplay() { + tView.channel.setText(timer.getChannelNumber() + " " + + timer.getChannelName()); + // tView.title.setText(timer.isVps() ? + // getString(R.string.timer_detail_title_vps, f.getTitle()) : + // f.getTitle()); + EventFormatter f = new EventFormatter(timer, true); + tView.title.setText(f.getTitle()); + tView.repeat.setText(getSelectedItems().toString(this, true)); + EpgCache.CACHE.remove(timer.getChannelId()); + EpgCache.NEXT_REFRESH.remove(timer.getChannelId()); + tView.vps.setChecked(timer.isVps()); + + } + + protected VdrManagerApp getApp() { + final VdrManagerApp app = (VdrManagerApp) getApplication(); + return app; + } + + public void add() { + updateDisplay(TimerOperation.CREATE); + } + + public void modify() { + updateDisplay(TimerOperation.MODIFY); + } + + public void onClick(final View view) { + switch (view.getId()) { + case R.id.timer_detail_day: { + final Calendar cal = new GregorianCalendar(); + cal.setTime(timer.getStart()); + final DatePickerDialog dialog = new DatePickerDialog(this, this, + cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), + cal.get(Calendar.DAY_OF_MONTH)); + dialog.show(); + break; + } + case R.id.timer_detail_start: { + final Calendar cal = new GregorianCalendar(); + cal.setTime(timer.getStart()); + editStart = true; + final TimePickerDialog dialog = new TimePickerDialog(this, this, + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), + true); + dialog.show(); + break; + } + case R.id.timer_detail_end: { + final Calendar cal = new GregorianCalendar(); + cal.setTime(timer.getStop()); + editStart = false; + final TimePickerDialog dialog = new TimePickerDialog(this, this, + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), + true); + dialog.show(); + break; + } + case R.id.timer_details_cancel: { + // finishActivity(REQUEST_CODE_TIMER_EDIT); + finish(); + break; + } + case R.id.timer_details_modify: + timer.setTitle(tView.title.getText().toString()); + timer.setVps(tView.vps.isChecked()); + timer.setPriority(getIntOr0(tView.priority)); + timer.setLifetime(getIntOr0(tView.lifecycle)); + + modifyTimer(timer); + // say(R.string.done); + break; + + case R.id.timer_details_save: { + timer.setTitle(tView.title.getText().toString()); + timer.setVps(tView.vps.isChecked()); + timer.setPriority(getIntOr0(tView.priority)); + timer.setLifetime(getIntOr0(tView.lifecycle)); + + createTimer(timer); + // say(R.string.done); + break; + } + + case R.id.timer_detail_repeat: { + + String[] weekdays = new DateFormatSymbols().getWeekdays(); + String[] values = new String[] { weekdays[Calendar.MONDAY], + weekdays[Calendar.TUESDAY], weekdays[Calendar.WEDNESDAY], + weekdays[Calendar.THURSDAY], weekdays[Calendar.FRIDAY], + weekdays[Calendar.SATURDAY], weekdays[Calendar.SUNDAY], }; + + final DaysOfWeek mNewDaysOfWeek = new DaysOfWeek( + getSelectedItems().mDays); + + final AlertDialog b = new AlertDialog.Builder(this) + .setMultiChoiceItems(values, + getSelectedItems().getBooleanArray(), + new DialogInterface.OnMultiChoiceClickListener() { + public void onClick(DialogInterface dialog, + int which, boolean isChecked) { + mNewDaysOfWeek.set(which, isChecked); + } + }) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + StringBuilder sb = new StringBuilder(7); + sb.append(mNewDaysOfWeek.isSet(0) ? 'M' + : '-'); + sb.append(mNewDaysOfWeek.isSet(1) ? 'T' + : '-'); + sb.append(mNewDaysOfWeek.isSet(2) ? 'W' + : '-'); + sb.append(mNewDaysOfWeek.isSet(3) ? 'T' + : '-'); + sb.append(mNewDaysOfWeek.isSet(4) ? 'F' + : '-'); + sb.append(mNewDaysOfWeek.isSet(5) ? 'S' + : '-'); + sb.append(mNewDaysOfWeek.isSet(6) ? 'S' + : '-'); + timer.setWeekdays(sb.toString()); + tView.repeat.setText(mNewDaysOfWeek + .toString( + TimerDetailsActivity.this, + true)); + } + }).create(); + + b.show(); + } + } + } + + DaysOfWeek getSelectedItems() { + String str = timer.getWeekdays(); + + DaysOfWeek dow = new DaysOfWeek(0); + if (str.length() != 7) { + return dow; + } + + dow.set(0, str.charAt(0) == 'M'); + dow.set(1, str.charAt(1) == 'T'); + dow.set(2, str.charAt(2) == 'W'); + dow.set(3, str.charAt(3) == 'T'); + dow.set(4, str.charAt(4) == 'F'); + dow.set(5, str.charAt(5) == 'S'); + dow.set(6, str.charAt(6) == 'S'); + + return dow; + } + + private int getIntOr0(EditText text) { + if (TextUtils.isEmpty(text.getText().toString())) { + return 0; + } + return Integer.valueOf(text.getText().toString()); + } + + protected void say(int res) { + Toast.makeText(this, res, Toast.LENGTH_SHORT).show(); + } + + public void onTimeSet(final TimePicker view, final int hourOfDay, + final int minute) { + if (editStart) { + timer.setStart(calculateTime(timer.getStart(), hourOfDay, minute, + null)); + } else { + timer.setStop(calculateTime(timer.getStop(), hourOfDay, minute, + timer.getStart())); + } + updateDates(timer.getStart(), timer.getStop()); + } + + public void onDateSet(final DatePicker view, final int year, + final int monthOfYear, final int dayOfMonth) { + timer.setStart(calculateDate(timer.getStart(), year, monthOfYear, + dayOfMonth)); + updateDates(timer.getStart(), timer.getStop()); + } + + private Date calculateDate(final Date oldDate, final int year, + final int monthOfYear, final int dayOfMonth) { + + final Calendar cal = new GregorianCalendar(); + cal.setTime(oldDate); + cal.set(Calendar.YEAR, year); + cal.set(Calendar.MONTH, monthOfYear); + cal.set(Calendar.DAY_OF_MONTH, dayOfMonth); + + return cal.getTime(); + } + + private Date calculateTime(final Date oldTime, final int hourOfDay, + final int minute, final Date startTime) { + + // set hour and minute + final Calendar cal = new GregorianCalendar(); + cal.setTime(oldTime); + cal.set(Calendar.HOUR_OF_DAY, hourOfDay); + cal.set(Calendar.MINUTE, minute); + + // go to the next day if end time before start time + if (startTime != null) { + if (cal.getTime().before(startTime)) { + cal.add(Calendar.DAY_OF_MONTH, 1); + } + } + + return cal.getTime(); + + } + + private void createTimer(Timer timer) { + final CreateTimerTask task = new CreateTimerTask(this, timer) { + @Override + public void finished(SvdrpEvent event) { + done(); + } + }; + task.start(); + } + + public void done() { + setResult(RESULT_OK); + finish(); + } + + private void modifyTimer(Timer timer) { + final ModifyTimerTask task = new ModifyTimerTask(this, timer, original) { + @Override + public void finished(SvdrpEvent event) { + done(); + } + }; + task.start(); + } + + /* + * Days of week code as a single int. 0x00: no day 0x01: Monday 0x02: + * Tuesday 0x04: Wednesday 0x08: Thursday 0x10: Friday 0x20: Saturday 0x40: + * Sunday + */ + static final class DaysOfWeek { + + private static int[] DAY_MAP = new int[] { Calendar.MONDAY, + Calendar.TUESDAY, Calendar.WEDNESDAY, Calendar.THURSDAY, + Calendar.FRIDAY, Calendar.SATURDAY, Calendar.SUNDAY, }; + + // Bitmask of all repeating days + private int mDays; + + DaysOfWeek(int days) { + mDays = days; + } + + public String toString(Context context, boolean showNever) { + StringBuilder ret = new StringBuilder(); + + // no days + if (mDays == 0) { + return showNever ? context.getText(R.string.never).toString() + : ""; + } + + // every day + if (mDays == 0x7f) { + return context.getText(R.string.every_day).toString(); + } + + // count selected days + int dayCount = 0, days = mDays; + while (days > 0) { + if ((days & 1) == 1) + dayCount++; + days >>= 1; + } + + // short or long form? + DateFormatSymbols dfs = new DateFormatSymbols(); + String[] dayList = (dayCount > 1) ? dfs.getShortWeekdays() : dfs + .getWeekdays(); + + // selected days + for (int i = 0; i < 7; i++) { + if ((mDays & (1 << i)) != 0) { + ret.append(dayList[DAY_MAP[i]]); + dayCount -= 1; + if (dayCount > 0) + ret.append(context.getText(R.string.day_concat)); + } + } + return ret.toString(); + } + + private boolean isSet(int day) { + return ((mDays & (1 << day)) > 0); + } + + public void set(int day, boolean set) { + if (set) { + mDays |= (1 << day); + } else { + mDays &= ~(1 << day); + } + } + + public void set(DaysOfWeek dow) { + mDays = dow.mDays; + } + + public int getCoded() { + return mDays; + } + + // Returns days of week encoded in an array of booleans. + public boolean[] getBooleanArray() { + boolean[] ret = new boolean[7]; + for (int i = 0; i < 7; i++) { + ret[i] = isSet(i); + } + return ret; + } + + public boolean isRepeatSet() { + return mDays != 0; + } + + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimerEventAdapter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimerEventAdapter.java new file mode 100644 index 0000000..0c39f81 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimerEventAdapter.java @@ -0,0 +1,31 @@ +package de.bjusystems.vdrmanager.gui; + +import android.content.Context; +import android.view.View; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.data.Timer; + +public class TimerEventAdapter extends TimeEventAdapter { + + public TimerEventAdapter(final Context context) { + super(context); + } + + public void sortItems() { + // sortItemsByChannel(); + } + + @Override + protected void handleState(EventListItemHolder itemHolder, + EventListItem item) { + super.handleState(itemHolder, item); + Timer r = (Timer) item.getEvent(); + if (r.isVps()) { + itemHolder.other.setVisibility(View.VISIBLE); + itemHolder.other.setImageResource(R.drawable.timer_vps); + } else { + itemHolder.other.setVisibility(View.GONE); + } + } +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimerListActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimerListActivity.java new file mode 100644 index 0000000..ae07e1c --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/TimerListActivity.java @@ -0,0 +1,291 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.data.EventListItem; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.utils.date.DateFormatter; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; +import de.bjusystems.vdrmanager.utils.svdrp.TimerClient; + +/** + * This class is used for showing all the existing timers + * + * @author bju + */ +public class TimerListActivity extends BaseTimerEditActivity<Timer> implements +OnItemClickListener { + + private static final int MENU_NEW_TIMER = 2; + + private static final int MENU_GROUP_NEW_TIMER = 2; + + protected static ArrayList<Timer> CACHE = new ArrayList<Timer>(); + + /* + * (non-Javadoc) + * + * @see + * de.bjusystems.vdrmanager.gui.BaseEventListActivity#onCreate(android.os + * .Bundle) + */ + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Attach view + // setContentView(getMainLayout()); + + // create an adapter + adapter = new TimerEventAdapter(this); + + // attach adapter to ListView + listView = (ListView) findViewById(R.id.timer_list); + listView.setAdapter(adapter); + listView.setFastScrollEnabled(true); + listView.setTextFilterEnabled(true); + + // set click listener + listView.setOnItemClickListener(this); + + // context menu wanted + registerForContextMenu(listView); + + // start query + startTimerQuery(); + } + + @Override + protected void onPause() { + super.onPause(); + } + + private void startTimerQuery() { + + if (checkInternetConnection() == false) { + return; + } + + // get timer client + final TimerClient timerClient = new TimerClient(getCertificateProblemDialog()); + + // create backgound task + final SvdrpAsyncTask<Timer, SvdrpClient<Timer>> task = new SvdrpAsyncTask<Timer, SvdrpClient<Timer>>( + timerClient); + + // create progress dialog + // progress = new SvdrpProgressDialog(this, timerClient); + + // attach listener + // task.addListener(progress); + addListener(task); + + // start task + task.run(); + } + + /* + * (non-Javadoc) + * + * @see + * de.bjusystems.vdrmanager.gui.BaseTimerEditActivity#getTimer(de.bjusystems + * .vdrmanager.data.EventListItem) + */ + @Override + protected Timer getTimer(final EventListItem item) { + return (Timer) item.getEvent(); + } + + /* + * (non-Javadoc) + * + * @see + * de.bjusystems.vdrmanager.gui.BaseEventListActivity#prepareTimer(de.bjusystems + * .vdrmanager.data.EventListItem) + */ +// @Override +// protected int prepareDetailsViewData(final EventListItem item, int position) { +// final VdrManagerApp app = (VdrManagerApp) getApplication(); +// // remember event for details view and timer things +// app.setCurrentEvent(item.getEvent()); +// app.setCurrentEpgList(CACHE); +// for(int i = 0; i < position; ++i){ +// if(CACHE.get(i) == item.getEvent()){ +// return i; +// } +// } +// +// return 0; +// } + + protected Comparator<Timer> getTimeComparator(final boolean reverse) { + return new Comparator<Timer>() { + TimeAndChannelComparator c = new TimeAndChannelComparator(); + @Override + public int compare(final Timer item1, final Timer item2) { + if (item1.isRecurring()) { + if(item2.isRecurring()){ + return 0; + } + return 1; + } + if (item2.isRecurring()) { + return -1; + } + return c.compare(item1, item2); + } + + }; + } + + + + @Override + protected boolean finishedSuccessImpl(final List<Timer> results) { + clearCache(); + for(final Timer r :results){ + CACHE.add(r); + } + pushResultCountToTitle(); + fillAdapter(); + return adapter.isEmpty() == false; + + } + + + protected void sort() { + /* */ + switch (sortBy) { + case MENU_GROUP_DEFAULT: { + Collections.sort(CACHE, getTimeComparator(false)); + break; + } + case MENU_GROUP_ALPHABET: { + Collections.sort(CACHE, new TitleComparator()); + break; + } + //case MENU_GROUP_CHANNEL: { + //sortItemsByChannel(results); + //} + } + } + + + @Override + protected void fillAdapter() { + + adapter.clear(); + + if (CACHE.isEmpty()) { + return; + } + + sort(); + + int day = -1; + final Calendar cal = Calendar.getInstance(); + + for (final Timer e : CACHE) { + if (e.isRecurring()) { + adapter.add(new EventListItem(e.getWeekdays())); + } else { + cal.setTime(e.getStart()); + final int eday = cal.get(Calendar.DAY_OF_YEAR); + if (eday != day) { + day = eday; + adapter.add(new EventListItem(new DateFormatter(cal) + .getDailyHeader())); + } + } + adapter.add(new EventListItem(e)); + } + adapter.notifyDataSetChanged(); + } + + // protected boolean finishedSuccessImpl() { + // adapter.clear(); + // sortItemsByTime(results); + // int day = -1; + // Calendar cal = Calendar.getInstance(); + // for (Timer e : results) { + // if (e.isRecurring()) { + // adapter.add(new EventListItem(e.getWeekdays())); + // } else { + // cal.setTime(e.getStart()); + // int eday = cal.get(Calendar.DAY_OF_YEAR); + // if (eday != day) { + // day = eday; + // adapter.add(new EventListItem(new DateFormatter(cal) + // .getDailyHeader())); + // } + // } + // adapter.add(new EventListItem(e)); + // } + // listView.setSelectionAfterHeaderView(); + // return adapter.isEmpty() == false; + // } + + @Override + protected boolean notifyDataSetChangedOnResume() { + return true; + } + + @Override + protected String getWindowTitle() { + return getString(R.string.action_menu_timers); + } + + @Override + protected int getMainLayout() { + return R.layout.timer_list; + } + + @Override + protected void refresh() { + startTimerQuery(); + } + + @Override + protected void retry() { + refresh(); + } + + @Override + protected int getAvailableSortByEntries() { + return R.array.epg_sort_by_time_alpha; + } + + public boolean onCreateOptionsMenu(final Menu menu) { + // MenuItem item; + // item = menu.add(MENU_GROUP_NEW_TIMER, MENU_NEW_TIMER, 0, + // R.string.new_timer); + // item.setIcon(android.R.drawable.ic_menu_add);; + // /item.setAlphabeticShortcut('r'); + + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.timer_list_menu, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + protected int getListNavigationIndex() { + return LIST_NAVIGATION_TIMERS; + } + + @Override + protected List<Timer> getCACHE() { + return CACHE; + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/Utils.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/Utils.java new file mode 100644 index 0000000..1116736 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/Utils.java @@ -0,0 +1,772 @@ +package de.bjusystems.vdrmanager.gui; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.net.ConnectivityManager; +import android.net.Uri; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.text.style.ForegroundColorSpan; +import android.util.Log; +import android.util.Pair; +import android.view.Gravity; +import android.widget.Toast; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.StringUtils; +import de.bjusystems.vdrmanager.app.C; +import de.bjusystems.vdrmanager.data.AudioTrack; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.Event; +import de.bjusystems.vdrmanager.data.EventContentGroup; +import de.bjusystems.vdrmanager.data.EventFormatter; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Recording; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.data.TimerMatch; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpEvent; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpException; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpListener; +import de.bjusystems.vdrmanager.utils.svdrp.SwitchChannelClient; + +public class Utils { + + public static final String TAG = Utils.class.getName(); + + public static final List EMPTY_LIST = new ArrayList(0); + public static final String[] EMPTY = new String[] {}; + public static final ForegroundColorSpan HIGHLIGHT_TEXT = new ForegroundColorSpan( + + Color.RED); + + public static CharSequence highlight(final String where, String what) { + if (TextUtils.isEmpty(what)) { + return where; + } + + final String str = where.toLowerCase(); + what = what.toLowerCase(); + final int idx = str.indexOf(what); + if (idx == -1) { + return where; + } + final SpannableString ss = new SpannableString(where); + ss.setSpan(HIGHLIGHT_TEXT, idx, idx + what.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return ss; + } + + public static Pair<Boolean, CharSequence> highlight2(final String where, + String what) { + if (TextUtils.isEmpty(what)) { + return Pair.create(Boolean.FALSE, (CharSequence) where); + } + + final String str = where.toLowerCase(); + what = what.toLowerCase(); + final int idx = str.indexOf(what); + if (idx == -1) { + return Pair.create(Boolean.FALSE, (CharSequence) where); + } + final SpannableString ss = new SpannableString(where); + ss.setSpan(HIGHLIGHT_TEXT, idx, idx + what.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + return Pair.create(Boolean.TRUE, (CharSequence) ss); + } + + public static int getProgress(final Date start, final Date stop) { + final long now = System.currentTimeMillis(); + return getProgress(now, start.getTime(), stop.getTime()); + } + + public static int getProgress(final Event e) { + if (e instanceof Recording == false) { + return getProgress(e.getStart(), e.getStop()); + } + final Recording r = ((Recording) e); + if (r.getTimerStopTime() == null) { + return getProgress(e.getStart(), e.getStop()); + } + return getProgress(r.getStart(), r.getTimerStopTime()); + + } + + /** + * @param now + * @param time + * @param time2 + * @return -1, is not not between start stop, + */ + private static int getProgress(final long now, final long start, + final long stop) { + if (now >= start && now <= stop) { + final long dura = stop - start; + final long prog = now - start; + return (int) (prog * 100 / dura); + } + return -1; + } + + public static boolean isLive(final Event event) { + final long now = System.currentTimeMillis(); + return now >= event.getStart().getTime() + && now <= event.getStop().getTime(); + } + + private static String trimToEmpty(final String str) { + if (str == null) { + return ""; + } + if (TextUtils.isEmpty(str)) { + return ""; + } + return str; + } + + private static String getBaseUrl() { + final StringBuilder sb = new StringBuilder(); + final Preferences p = Preferences.getPreferences(); + String auth = trimToEmpty(p.getStreamingUsername()) + ":" + + trimToEmpty(p.getStreamingPassword()); + if (auth.length() == 1) { + auth = ""; + } else { + auth += "@"; + } + + sb.append("http://").append(auth).append(p.getHost()).append(":") + .append(p.getStreamPort()); + return sb.toString(); + } + + private static String getStreamUrl(final String chn) { + // "http://192.168.1.119:3000/TS/" + final Preferences p = Preferences.getPreferences(); + final StringBuilder sb = new StringBuilder(); + sb.append(getBaseUrl()).append("/").append(p.getStreamFormat()) + .append("/").append(chn); + + return sb.toString(); + } + + private static String getRemuxStreamUrl(final String chn) { + // "http://192.168.1.119:3000/TS/" + final StringBuilder sb = new StringBuilder(); + final Preferences p = Preferences.getPreferences(); + sb.append(getBaseUrl()).append("/").append(p.getRemuxCommand()) + .append(";").append(p.getRemuxParameter()).append("/") + .append(chn); + return sb.toString(); + } + + public static void stream(final Activity activity, final Event event) { + stream(activity, event.getStreamId()); + } + + public static void stream(final Activity activity, final Channel channel) { + stream(activity, channel.getId()); + } + + public static void stream(final Activity activity, final String idornr) { + + if (Preferences.get().isEnableRemux() == false) { + final String url = getStreamUrl(idornr); + startStream(activity, url); + return; + } + + final String sf = Preferences.get().getStreamFormat(); + final String ext = activity.getString(R.string.remux_title); + new AlertDialog.Builder(activity) + .setTitle(R.string.stream_via_as) + // + .setItems( + new String[] { + activity.getString(R.string.stream_as, sf), + activity.getString(R.string.stream_via, ext) },// TODO + // add + // here + // what + // will + // be + // used + new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, + final int which) { + String url = null; + switch (which) { + case 0: + url = getStreamUrl(idornr); + break; + case 1: + url = getRemuxStreamUrl(idornr); + break; + } + startStream(activity, url); + } + }).create().show(); + } + + public static void startStream(final Activity activity, final String url) { + try { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse(url.toString()), "video/*"); + activity.startActivityForResult(intent, 1); + } catch (final ActivityNotFoundException anfe) { + Log.w(TAG, anfe); + Toast.makeText(activity, anfe.getLocalizedMessage(), + Toast.LENGTH_SHORT).show(); + } + } + + public static final String md5(final String s) { + try { + // Create MD5 Hash + final MessageDigest digest = java.security.MessageDigest + .getInstance("MD5"); + digest.update(s.getBytes()); + final byte messageDigest[] = digest.digest(); + + // Create Hex String + final StringBuffer hexString = new StringBuffer(); + for (int i = 0; i < messageDigest.length; i++) { + String h = Integer.toHexString(0xFF & messageDigest[i]); + while (h.length() < 2) { + h = "0" + h; + } + hexString.append(h); + } + return hexString.toString(); + + } catch (final NoSuchAlgorithmException e) { + Log.w(TAG, e); + } + return ""; + } + + public static int getDuration(final Event event) { + final long millis = event.getDuration(); + final int minuts = (int) (millis / 1000 / 60); + return minuts; + } + + public static void shareEvent(final Activity activity, final Event event) { + final Intent share = new Intent(android.content.Intent.ACTION_SEND); + share.setType("text/plain"); + StringBuilder sb = new StringBuilder(); + sb.append(event.getTitle()); + sb.append("\n"); + final EventFormatter ef = new EventFormatter(event, false); + sb.append(ef.getDate()).append(" ").append(ef.getTime()); + final String title = sb.toString(); + share.putExtra(android.content.Intent.EXTRA_SUBJECT, sb.toString()); + sb = new StringBuilder(); + sb.append(title).append("\n\n"); + sb.append(event.getChannelNumber() + " " + event.getChannelName()); + sb.append("\n\n"); + sb.append(ef.getShortText()); + sb.append("\n\n"); + sb.append(ef.getDescription()); + sb.append("\n"); + share.putExtra(android.content.Intent.EXTRA_TEXT, sb.toString()); + activity.startActivity(Intent.createChooser(share, + activity.getString(R.string.share_chooser))); + } + + public static void addCalendarEvent(final Activity activity, + final Event event) { + final Intent intent = new Intent(Intent.ACTION_EDIT); + intent.setType("vnd.android.cursor.item/event"); + intent.putExtra("title", event.getTitle()); + intent.putExtra("description", event.getShortText()); + intent.putExtra("beginTime", event.getStart().getTime()); + intent.putExtra("endTime", event.getStop().getTime()); + activity.startActivity(intent); + } + + public static String mapSpecialChars(final String src) { + if (src == null) { + return ""; + } + return src.replace("|##", C.DATA_SEPARATOR).replace("||#", "\n"); + } + + public static String unMapSpecialChars(final String src) { + if (src == null) { + return ""; + } + return src.replace(C.DATA_SEPARATOR, "|##").replace("\n", "||#"); + } + + public static PackageInfo getPackageInfo(final Context ctx) { + PackageInfo pi = null; + try { + pi = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), + PackageManager.GET_ACTIVITIES); + } catch (final PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + return pi; + } + + public static boolean checkInternetConnection(final Context ctx) { + final ConnectivityManager cm = (ConnectivityManager) ctx + .getSystemService(Context.CONNECTIVITY_SERVICE); + // test for connection + if (cm.getActiveNetworkInfo() != null + && cm.getActiveNetworkInfo().isConnectedOrConnecting()) { + return true; + } + return false; + } + + private static String getRecordingStream(final Activity ctx, + final Recording rec) { + + final String m = Preferences.get().getRecStreamMethod(); + + final StringBuilder url = new StringBuilder(); + + if (StringUtils.equals(m, "vdr-live")) { + url.append("http://") + .append(Preferences.get().getHost()) + // + .append(":") + .append(Integer.valueOf(Preferences.get().getLivePort())) + // + .append("/recstream.html?recid=recording_") + .append(Utils.md5(rec.getFileName())); + // http://192.168.1.119:8008/b0cdedeed2d36508dfd924f0876a851b + final String urlstring = url.toString(); + return urlstring; + } else if (StringUtils.equals(m, "vdr-streamdev")) { + url.append("http://").append(Preferences.get().getHost()) + // + .append(":") + .append(Integer.valueOf(Preferences.get().getStreamPort())) + // + .append("/").append(rec.getDevInode()); + } else if (StringUtils.equals(m, "vdr-smarttvweb")) { + + String type = Preferences.get().getSmarttvewebType(); + + url.append("http://") + .append(Preferences.get().getHost()) + // + .append(":") + .append(Integer.valueOf(Preferences.get() + .getSmarttvewebPort())) + // + .append(Utils.encodeUrlPath(rec.getFileName())); + if (StringUtils.equals(type, "has") == true) { + url.append("/manifest-seg.mpd"); + } else if (StringUtils.equals(type, "hls")) { + url.append("/manifest-seg.m3u8"); + } + } + return url.toString(); + } + + public static void streamRecording(final Activity ctx, final Recording rec) { + final String urlstring = getRecordingStream(ctx, rec); + Log.d(TAG, "try stream: " + urlstring); + Utils.startStream(ctx, urlstring); + } + + public static void switchTo(final Activity activity, final Channel channel) { + switchTo(activity, channel.getId(), channel.getName()); + } + + /** + * @param ctx + * @param id + * @param name + * Optional für die Anzeige + */ + public static void switchTo(final Activity activity, final String id, + final String name) { + + final SwitchChannelClient scc = new SwitchChannelClient(id, + new CertificateProblemDialog(activity)); + final SvdrpAsyncTask<String, SwitchChannelClient> task = new SvdrpAsyncTask<String, SwitchChannelClient>( + scc); + task.addSvdrpListener(new SvdrpListener() { + @Override + public void svdrpEvent(final SvdrpEvent event) { + if (event == SvdrpEvent.FINISHED_SUCCESS) { + Utils.say(activity, activity.getString( + R.string.switching_success, (name != null ? name + : id))); + } else if (event == SvdrpEvent.CONNECT_ERROR + || event == SvdrpEvent.FINISHED_ABNORMALY + || event == SvdrpEvent.ABORTED + || event == SvdrpEvent.ERROR + || event == SvdrpEvent.CACHE_HIT) { + Utils.say(activity, activity.getString( + R.string.switching_failed, (name != null ? name + : id), event.name())); + } + } + + public void svdrpException(final SvdrpException e) { + Log.w(TAG, e.getMessage(), e); + Utils.say(activity, e.getMessage()); + } + }); + task.run(); + } + + public static void say(final Context ctx, final String msg) { + final Toast t = Toast.makeText(ctx, msg, Toast.LENGTH_SHORT); + t.setGravity(Gravity.CENTER, 0, 0); + t.show(); + } + public static String encodeUrlPath(String path){ + return path.replaceAll("%", "%25"); + } + + public static void say(final Context ctx, final int msg) { + final Toast t = Toast.makeText(ctx, msg, Toast.LENGTH_SHORT); + t.setGravity(Gravity.CENTER, 0, 0); + t.show(); + } + + /** + * Formats the date and time based on user's phone date/time preferences. + * + * @param context + * the context + * @param time + * the time in milliseconds + */ + + public static String formatDateTime(final Context context, final long time) { + return android.text.format.DateFormat.getDateFormat(context).format( + time) + + " " + + DateUtils.formatDateTime(context, time, + DateUtils.FORMAT_SHOW_TIME).toString(); + } + + public static int getTimerStateDrawable(final TimerMatch match, + final int full, final int begin, final int end, final int conflict) { + + switch (match) { + case Begin: + return begin; + case End: + return end; + case Conflict: + return conflict; + default: + return full; + } + } + + public static String formatAudio(final Context context, + final List<AudioTrack> tracks) { + + final StringBuilder sb = new StringBuilder(); + + String sep = ""; + for (final AudioTrack a : tracks) { + sb.append(sep).append(a.display); + if (a.type.equals("d")) { + sb.append(" (") + .append(context.getString(R.string.audio_track_dolby)) + .append(")"); + } + sep = ", "; + } + return sb.toString(); + + } + + public static TimerMatch getTimerMatch(Event event, Timer timer) { + if (timer == null) { + return null; + } + TimerMatch timerMatch = null; + Date start = event.getStart(); + Date stop = event.getStop(); + if (start.before(timer.getStart())) { + timerMatch = TimerMatch.End; + } else if (stop.after(timer.getStop())) { + timerMatch = TimerMatch.Begin; + } else { + timerMatch = TimerMatch.Full; + } + return timerMatch; + } + + public static int contentToString(int c) { + ; + switch (c & 0xF0) { + case EventContentGroup.MovieDrama: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$Movie__Drama; + case 0x01: + return R.string.Content$Detective__Thriller; + case 0x02: + return R.string.Content$Adventure__Western__War; + case 0x03: + return R.string.Content$Science_Fiction__Fantasy__Horror; + case 0x04: + return R.string.Content$Comedy; + case 0x05: + return R.string.Content$Soap__Melodrama__Folkloric; + case 0x06: + return R.string.Content$Romance; + case 0x07: + return R.string.Content$Serious__Classical__Religious__Historical_Movie__Drama; + case 0x08: + return R.string.Content$Adult_Movie__Drama; + } + case EventContentGroup.NewsCurrentAffairs: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$News__Current_Affairs; + case 0x01: + return R.string.Content$News__Weather_Report; + case 0x02: + return R.string.Content$News_Magazine; + case 0x03: + return R.string.Content$Documentary; + case 0x04: + return R.string.Content$Discussion__Inverview__Debate; + } + + case EventContentGroup.Show: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$Show__Game_Show; + case 0x01: + return R.string.Content$Game_Show__Quiz__Contest; + case 0x02: + return R.string.Content$Variety_Show; + case 0x03: + return R.string.Content$Talk_Show; + } + + case EventContentGroup.Sports: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$Sports; + case 0x01: + return R.string.Content$Special_Event; + case 0x02: + return R.string.Content$Sport_Magazine; + case 0x03: + return R.string.Content$Football__Soccer; + case 0x04: + return R.string.Content$Tennis__Squash; + case 0x05: + return R.string.Content$Team_Sports; + case 0x06: + return R.string.Content$Athletics; + case 0x07: + return R.string.Content$Motor_Sport; + case 0x08: + return R.string.Content$Water_Sport; + case 0x09: + return R.string.Content$Winter_Sports; + case 0x0A: + return R.string.Content$Equestrian; + case 0x0B: + return R.string.Content$Martial_Sports; + } + + case EventContentGroup.ChildrenYouth: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$Childrens__Youth_Programme; + case 0x01: + return R.string.Content$Preschool_Childrens_Programme; + case 0x02: + return R.string.Content$Entertainment_Programme_for_6_to_14; + case 0x03: + return R.string.Content$Entertainment_Programme_for_10_to_16; + case 0x04: + return R.string.Content$Informational__Educational__School_Programme; + case 0x05: + return R.string.Content$Cartoons__Puppets; + } + + case EventContentGroup.MusicBalletDance: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$Music__Ballet__Dance; + case 0x01: + return R.string.Content$Rock__Pop; + case 0x02: + return R.string.Content$Serious__Classical_Music; + case 0x03: + return R.string.Content$Folk__Tradional_Music; + case 0x04: + return R.string.Content$Jazz; + case 0x05: + return R.string.Content$Musical__Opera; + case 0x06: + return R.string.Content$Ballet; + } + + case EventContentGroup.ArtsCulture: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$Arts__Culture; + case 0x01: + return R.string.Content$Performing_Arts; + case 0x02: + return R.string.Content$Fine_Arts; + case 0x03: + return R.string.Content$Religion; + case 0x04: + return R.string.Content$Popular_Culture__Traditional_Arts; + case 0x05: + return R.string.Content$Literature; + case 0x06: + return R.string.Content$Film__Cinema; + case 0x07: + return R.string.Content$Experimental_Film__Video; + case 0x08: + return R.string.Content$Broadcasting__Press; + case 0x09: + return R.string.Content$New_Media; + case 0x0A: + return R.string.Content$Arts__Culture_Magazine; + case 0x0B: + return R.string.Content$Fashion; + } + + case EventContentGroup.SocialPoliticalEconomics: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$Social__Political__Economics; + case 0x01: + return R.string.Content$Magazine__Report__Documentary; + case 0x02: + return R.string.Content$Economics__Social_Advisory; + case 0x03: + return R.string.Content$Remarkable_People; + } + + case EventContentGroup.EducationalScience: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$Education__Science__Factual; + case 0x01: + return R.string.Content$Nature__Animals__Environment; + case 0x02: + return R.string.Content$Technology__Natural_Sciences; + case 0x03: + return R.string.Content$Medicine__Physiology__Psychology; + case 0x04: + return R.string.Content$Foreign_Countries__Expeditions; + case 0x05: + return R.string.Content$Social__Spiritual_Sciences; + case 0x06: + return R.string.Content$Further_Education; + case 0x07: + return R.string.Content$Languages; + } + + case EventContentGroup.LeisureHobbies: + switch (c & 0x0F) { + default: + case 0x00: + return R.string.Content$Leisure__Hobbies; + case 0x01: + return R.string.Content$Tourism__Travel; + case 0x02: + return R.string.Content$Handicraft; + case 0x03: + return R.string.Content$Motoring; + case 0x04: + return R.string.Content$Fitness_and_Health; + case 0x05: + return R.string.Content$Cooking; + case 0x06: + return R.string.Content$Advertisement__Shopping; + case 0x07: + return R.string.Content$Gardening; + } + case EventContentGroup.Special: + switch (c & 0x0F) { + case 0x00: + return R.string.Content$Original_Language; + case 0x01: + return R.string.Content$Black_and_White; + case 0x02: + return R.string.Content$Unpublished; + case 0x03: + return R.string.Content$Live_Broadcast; + default: + ; + } + break; + default: + ; + } + return R.string.Content$Unknown; + } + + public static String getContenString(Context ctx, int[] contents) { + + if (contents.length == 0) { + return ""; + } + + StringBuilder sb = new StringBuilder(); + + String sep = ""; + + for (int content : contents) { + + sb.append(sep).append(ctx.getString(contentToString(content))); + sep = ", "; + } + + return sb.toString(); + } + + public static boolean isSerie(int[] contents) { + if (contents.length == 0) { + return false; + } + + for (int c : contents) { + if (c == 21) { + return true; + + } + } + + return false; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/VdrListActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/VdrListActivity.java new file mode 100644 index 0000000..a499d00 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/VdrListActivity.java @@ -0,0 +1,287 @@ +package de.bjusystems.vdrmanager.gui; + +import java.util.ArrayList; +import java.util.List; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Typeface; +import android.os.Bundle; +import android.text.SpannableString; +import android.text.style.UnderlineSpan; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.ArrayAdapter; +import android.widget.TextView; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.Intents; +import de.bjusystems.vdrmanager.backup.BackupSettingsActivity; +import de.bjusystems.vdrmanager.backup.IntentUtils; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Vdr; +import de.bjusystems.vdrmanager.data.db.DBAccess; + +public class VdrListActivity extends ListActivity implements + OnItemClickListener, OnItemLongClickListener { + + private static final String TAG = VdrListActivity.class.getName(); + + List<Vdr> list = new ArrayList<Vdr>(); + + ArrayAdapter<Vdr> adapter = null; + + // Cursor cursor; + + String[] listItems = {}; + + private boolean emptyConfig = false; + + // private void initCursor() { + // + // //if (cursor != null) { + // //if (!cursor.isClosed()) { + // //cursor.close(); + // // } + // //} + // try { + // cursor = getHelper().getVdrCursor(); + // //startManagingCursor(cursor); + // } catch (Exception ex) { + // Log.w(TAG,ex); + // } + // } + + private void populateIntent() { + emptyConfig = getIntent().getBooleanExtra(Intents.EMPTY_CONFIG, + Boolean.FALSE); + } + + static class Holder { + public TextView text1; + public TextView text2; + } + + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + populateIntent(); + + setContentView(R.layout.vdr_list_add_delete); + + // initCursor(); + final Vdr cur = Preferences.get().getCurrentVdr(); + adapter = new ArrayAdapter<Vdr>(this, + android.R.layout.simple_list_item_2, list) { + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + // recycle view? + Holder holder = null; + View view = convertView; + if (view == null) { + view = getLayoutInflater().inflate( + android.R.layout.simple_list_item_2, null); + holder = new Holder(); + + holder.text1 = (TextView) view + .findViewById(android.R.id.text1); + holder.text2 = (TextView) view + .findViewById(android.R.id.text2); + view.setTag(holder); + } else { + holder = (Holder) view.getTag(); + } + + Vdr vdr = getItem(position); + String name = (vdr.getName() != null ? vdr.getName() : ""); + String host = vdr.getHost(); + holder.text2.setText(host); + + if (cur != null && cur.getId() == vdr.getId()) { + SpannableString content = new SpannableString(name); + content.setSpan(new UnderlineSpan(), 0, content.length(), 0); + holder.text1.setText(content); + // text1.setText(text1.getText()); + } else { + holder.text1.setTypeface(Typeface.DEFAULT); + holder.text1.setText(name); + } + return view; + } + + }; + + // adapter = new ArrayAdapter<Vdr>( + // "name", "host" }, new int[] { android.R.id.text1, + // android.R.id.text2}) { + // @Override + // public void bindView(View view, Context context, + // Cursor cursor) { + // + // TextView text1 = (TextView)view.findViewById(android.R.id.text1); + // TextView text2 = (TextView)view.findViewById(android.R.id.text2); + // int id = cursor.getInt(cursor.getColumnIndex("_id")); + // String name = cursor.getString(cursor.getColumnIndex("name")); + // String host = cursor.getString(cursor.getColumnIndex("host")); + // text2.setText(host); + // + // if(cur != null && cur.getId() == id) { + // SpannableString content = new SpannableString(name); + // content.setSpan(new UnderlineSpan(), 0, content.length(), 0); + // text1.setText(content); + // //text1.setText(text1.getText()); + // } else { + // text1.setTypeface(Typeface.DEFAULT); + // text1.setText(name); + // } + // + // + // } + // + // }; + setListAdapter(adapter); + registerForContextMenu(getListView()); + getListView().setOnItemClickListener(this); + getListView().setOnItemLongClickListener(this); + } + + /* + * (non-Javadoc) + * + * @see + * android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget + * .AdapterView, android.view.View, int, long) + */ + public void onItemClick(AdapterView<?> parent, View view, int position, + long id) { + editVdr(adapter.getItem(position).getId()); + } + + /** + * Start {@link VdrPreferencesActivity} to create or edit a vdr + * + * @param id + * may be null. Then a new vdr is created + */ + private void editVdr(Integer id) { + Intent intent = new Intent(this, VdrPreferencesActivity.class); + intent.putExtra(Intents.VDR_ID, id); + startActivityForResult(intent, Intents.EDIT_VDR); + } + + /* + * (non-Javadoc) + * + * @see android.app.Activity#onActivityResult(int, int, + * android.content.Intent) + */ + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + + if (resultCode != RESULT_OK) { + return; + } + if (requestCode == Intents.EDIT_VDR) { + refresh(); + return; + } + + } + + @Override + protected void onResume() { + refresh(); + super.onResume(); + } + + @Override + public void onBackPressed() { + if (list.isEmpty()) { + finish(); + return; + } + if (emptyConfig) { + Intent intent = new Intent(this, VdrManagerActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + finish(); + } else { + super.onBackPressed(); + } + } + + /** + * Refresh the list + */ + private void refresh() { + list.clear(); + list.addAll(DBAccess.get(this).getVdrDAO().queryForAll()); + adapter.notifyDataSetChanged(); + } + + public boolean onItemLongClick(AdapterView<?> parent, View view, + final int position, final long id) { + + new AlertDialog.Builder(this) + .setMessage(R.string.vdr_device_delete_qeustion)// + .setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + if (DBAccess + .get(VdrListActivity.this) + .getVdrDAO() + .deleteById( + adapter.getItem(position) + .getId()) > 0) { + if (Preferences.get().getCurrentVdrContext( + VdrListActivity.this) == id) { + Preferences.setCurrentVdr( + VdrListActivity.this, null); + } + refresh(); + } + + } + })// + .setNegativeButton(android.R.string.cancel, null)// + .create()// + .show(); + return false; + } + + @Override + public final boolean onCreateOptionsMenu(final Menu menu) { + super.onCreateOptionsMenu(menu); + + final MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.vdrlist, menu); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.main_menu_vdrlist_restore) { + + Intent intent = IntentUtils.newIntent(this, + BackupSettingsActivity.class); + startActivity(intent); + return true; + } else if (item.getItemId() == R.id.main_menu_vdrlist_add) { + editVdr(null); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/VdrManagerActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/VdrManagerActivity.java new file mode 100644 index 0000000..729d75c --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/VdrManagerActivity.java @@ -0,0 +1,341 @@ +package de.bjusystems.vdrmanager.gui; + +import android.app.AlertDialog; +import android.app.SearchManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.SearchRecentSuggestions; +import android.support.v4.view.MenuItemCompat; +import android.support.v7.app.ActionBarActivity; +import android.support.v7.widget.SearchView; +import android.support.v7.widget.SearchView.OnQueryTextListener; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Toast; + +import com.j256.ormlite.android.AndroidDatabaseResults; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.app.Intents; +import de.bjusystems.vdrmanager.app.VdrManagerApp; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Vdr; +import de.bjusystems.vdrmanager.data.db.DBAccess; +import de.bjusystems.vdrmanager.data.db.EPGSearchSuggestionsProvider; +import de.bjusystems.vdrmanager.remote.RemoteActivity; +import de.bjusystems.vdrmanager.utils.wakeup.AsyncWakeupTask; + +public class VdrManagerActivity extends ActionBarActivity implements + OnClickListener, OnQueryTextListener { + + public static final String TAG = "VdrManagerActivity"; + + public static final String VDR_PORTAL = "http://www.vdr-portal.de"; + + private SearchView search; + + private View actionMenuWakup; + private View actionMenuRemote; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Preferences.initVDR(this); + + // if(Preferences.get().getCurrentVdr() == null){ + // finish(); + // return; + // } android.support.v7.appcompat.R + + if (Preferences.initVDR(this) == false) { + final Intent intent = new Intent(); + intent.setClass(this, VdrListActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(Intents.EMPTY_CONFIG, Boolean.TRUE); + startActivity(intent); + Toast.makeText(this, R.string.no_vdr, Toast.LENGTH_SHORT).show(); + finish(); + return; + } + + Preferences.setLocale(this); + + // this.getActionBar().setDisplayShowCustomEnabled(true); + // this.getActionBar().setDisplayShowTitleEnabled(false); + // setTitle(getString(R.string.app_name)); + // attach view + setContentView(R.layout.vdrmanager); + + // Preferences.loadPreferences(this); + + findViewById(R.id.action_menu_channels).setOnClickListener(this); + findViewById(R.id.action_menu_recordings).setOnClickListener(this); + findViewById(R.id.action_menu_timers).setOnClickListener(this); + findViewById(R.id.action_menu_epg).setOnClickListener(this); + findViewById(R.id.action_menu_remote).setOnClickListener(this); +// View v = findViewById(R.id.action_menu_search); +// if (v != null) { +// v.setOnClickListener(this); +// } + //findViewById(R.id.main_logo).setOnClickListener(this); + actionMenuWakup = findViewById(R.id.action_menu_wakeup); + actionMenuRemote = findViewById(R.id.action_menu_remote); + // add and register buttons + // createButtons(); + } + + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main_menu, menu); + + // search = new SearchView(getSupportActionBar().getThemedContext()); + search = (SearchView) MenuItemCompat.getActionView( menu.findItem(R.id.menu_search)); + + // search = (SearchView) + // .getActionView(); + // + // Object o = menu.findItem(R.id.menu_search); + + SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); + search.setSearchableInfo(searchManager + .getSearchableInfo(getComponentName())); + + //search.setOnQueryTextListener(this); + return true; + } + + @Override + protected void onResume() { + Preferences.setLocale(this); + if (Preferences.get().isWakeupEnabled() == false) { + actionMenuWakup.setVisibility(View.GONE); + actionMenuWakup.setOnClickListener(null); + } else { + actionMenuWakup.setVisibility(View.VISIBLE); + actionMenuWakup.setOnClickListener(this); + } + + if(Preferences.get().isRemoteEnabled() == false){ + actionMenuRemote.setVisibility(View.GONE); + actionMenuRemote.setOnClickListener(null); + } else { + actionMenuRemote.setVisibility(View.VISIBLE); + actionMenuRemote.setOnClickListener(this); + } + + + super.onResume(); + } + + @Override + public boolean onOptionsItemSelected( + final MenuItem item) { + + switch (item.getItemId()) { + case R.id.main_menu_preferences: { + Intent intent = new Intent(this, PreferencesActivity.class); + int flags = Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_SINGLE_TOP + | Intent.FLAG_ACTIVITY_CLEAR_TOP; + intent.setFlags(flags); + startActivity(intent); + finish(); + break; + } + case R.id.main_menu_info: { + if(isFinishing()){ + break; + } + About.show(this); + break; + } + case R.id.main_menu_exit: { + finish(); + break; + } + + case R.id.main_menu_clear_search: { + SearchRecentSuggestions suggestions = new SearchRecentSuggestions( + this, EPGSearchSuggestionsProvider.AUTHORITY, + EPGSearchSuggestionsProvider.MODE); + suggestions.clearHistory(); + break; + } + + // case R.id.menu_search: { + // if(Build.VERSION.SDK_INT <11){ + // onSearchRequested(); + // } + // break; + // } + case R.id.main_menu_goto: { + try { + final Cursor cursor = ((AndroidDatabaseResults) DBAccess + .get(this).getVdrDAO().iterator().getRawResults()) + .getRawCursor(); + startManagingCursor(cursor); + final AlertDialog ad = new AlertDialog.Builder(this) + .setSingleChoiceItems(cursor, findVdrCursor(cursor), + "name", new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, + int which) { + cursor.moveToPosition(which); + int id = cursor.getInt(cursor + .getColumnIndex("_id")); + Vdr vdr = DBAccess + .get(VdrManagerActivity.this) + .getVdrDAO().queryForId(id); + if (vdr == null) { + Toast.makeText( + VdrManagerActivity.this, + R.string.main_menu_goto_no_vdr, + Toast.LENGTH_SHORT).show(); + } else { + Preferences.setCurrentVdr( + VdrManagerActivity.this, + vdr); + Toast.makeText( + VdrManagerActivity.this, + getString( + R.string.main_menu_switched_to, + vdr.getName()), + Toast.LENGTH_SHORT).show(); + Intent intent = getIntent(); + overridePendingTransition(0, 0); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + finish(); + + overridePendingTransition(0, 0); + startActivity(intent); + } + dialog.dismiss(); + } + })// + .setTitle(R.string.main_menu_goto_title)// + .create(); + ad.show(); + + } catch (Exception ex) { + Log.w(TAG, ex); + } + + break; + } + } + return true; + } + + private int findVdrCursor(Cursor c) { + if (Preferences.get().getCurrentVdr() == null) { + return -1; + } + + int cid = Preferences.get().getCurrentVdr().getId(); + + int position = 0; + c.moveToPosition(-1); + while (c.moveToNext()) { + if (c.getInt(c.getColumnIndex("_id")) == cid) { + break; + } + position++; + } + return position; + } + + @Override + public void onBackPressed() { + if (Preferences.get().isQuiteOnBackButton()) { + finish(); + } else { + super.onBackPressed(); + } + + try { + // reassign a new and empty key store + ((VdrManagerApp)getApplication()).initSessionKeyStore(); + } catch (final Exception e) { + Log.e(getClass().getName(), "Can't clear session key store"); + } + + } + + public void startActivity(Class<?> clazz) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setClass(this, clazz); + startActivity(intent); + } + + public void onClick(View v) { + int id = v.getId(); + + switch (id) { + case R.id.action_menu_channels: + startActivity(ChannelListActivity.class); + break; + case R.id.action_menu_recordings: + startActivity(RecordingListActivity.class); + break; + case R.id.action_menu_timers: + startActivity(TimerListActivity.class); + break; + case R.id.action_menu_epg: + startActivity(TimeEpgListActivity.class); + break; +// case R.id.action_menu_search: +// onSearchRequested(); +// break; + case R.id.action_menu_wakeup: + final AsyncWakeupTask wakeupTask = new AsyncWakeupTask(this); + wakeupTask.execute(); + break; + case R.id.main_logo: + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(VDR_PORTAL)); + startActivity(i); + break; + + case R.id.action_menu_remote: + startActivity(RemoteActivity.class); + break; + + } + + } + + protected void startSearchManager() { + Bundle appData = new Bundle(); + startSearch(null, false, appData, false); + } + + @Override + public boolean onSearchRequested() { + search.setVisibility(View.VISIBLE); + // Bundle appData = new Bundle(); + // appData.putBoolean(SearchableActivity.JARGON, true); + // startSearch(null, false, appData, false); + return true; + } + + @Override + public boolean onQueryTextSubmit(String query) { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + // TODO Auto-generated method stub + return false; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/VdrPreferencesActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/VdrPreferencesActivity.java new file mode 100644 index 0000000..0d01737 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/gui/VdrPreferencesActivity.java @@ -0,0 +1,508 @@ +package de.bjusystems.vdrmanager.gui; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +import org.fueri.reeldroid.network.DeviceManager; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.view.View; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.VdrSharedPreferencesImpl; +import de.bjusystems.vdrmanager.ZonePicker; +import de.bjusystems.vdrmanager.app.Intents; +import de.bjusystems.vdrmanager.data.FetchEditTextPreference; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Vdr; +import de.bjusystems.vdrmanager.data.db.DBAccess; +import de.bjusystems.vdrmanager.tasks.VoidAsyncTask; + +public class VdrPreferencesActivity extends BasePreferencesActivity implements + OnSharedPreferenceChangeListener, OnPreferenceClickListener { + + public static final int REQUEST_CODE_PICK_A_TIME_ZONE = 1; + + Vdr vdr; + + VdrSharedPreferencesImpl pref; + + int id = -1; + + @Override + public SharedPreferences getSharedPreferences(String name, int mode) { + return this.pref; + } + + @Override + public Preference findPreference(CharSequence key) { + return super.findPreference(key); + } + + @Override + protected void updateSummary(Preference ep) { + if (ep.getKey().equals("key_timezone")) { + String text = vdr.getServerTimeZone(); + if (text == null) { + return; + } + setSummary(text, ep); + return; + } + super.updateSummary(ep); + } + + private boolean isNew = false; + + private boolean hasChanged = false; + + private void initVDRInstance() { + id = getIntent().getIntExtra(Intents.VDR_ID, -1); + if (id == -1) {// new vdr + vdr = new Vdr(); + isNew = true; + } else {// edit + Vdr v = DBAccess.get(this).getVdrDAO().queryForId(id); + if (v != null) { + vdr = v; + } else { + vdr = new Vdr(); + id = -1; + isNew = true; + } + } + pref = new VdrSharedPreferencesImpl(vdr, DBAccess.get(this).getVdrDAO()); + } + + public static String ARP_CACHE = "/proc/net/arp"; + + /** + * return mac address as a string. + * + * @param ip + * @return + */ + public static String getMacFromArpCache(String ip) { + + if (ip == null) { + return null; + } + + BufferedReader br = null; + + try { + br = new BufferedReader(new FileReader(ARP_CACHE)); + + String line; + + while ((line = br.readLine()) != null) { + String[] values = line.split("\\s+"); + if (values != null && values.length >= 4 + && ip.equals(values[0])) { + // format check + String mac = values[3]; + if (mac.matches("..:..:..:..:..:..")) { + return mac; + } else { + return null; + } + } + } + } catch (Exception e) { + + } finally { + try { + br.close(); + } catch (IOException e) { + + } + } + return null; + } + + // private String getIp() throws Exception { + // final Preferences prefs = Preferences.get(); + // String host = prefs.getSvdrpHost(); + // return InetAddress.getByName(host).getHostAddress(); + // } + + private void ping(String ip, int port) throws Exception { + Socket socket = new Socket(); + socket.connect(new InetSocketAddress(ip, port), 5 * 1000); + socket.setSoTimeout(5 * 1000); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + + super.onCreate(savedInstanceState); + + initVDRInstance(); + + this.addPreferencesFromResource(R.xml.vdr_prefs); + + // this.getPreferenceManager().setSharedPreferencesName(Preferences.getPreferenceFile(this)); + + pref.registerOnSharedPreferenceChangeListener(this); + + String recstream = pref.getString("key_recstream_method", "vdr-live"); + + if (recstream.equals("vdr-live") == false) { + Preference p = findPreference("key_live_port"); + p.setEnabled(false); + // PreferenceCategory cat = (PreferenceCategory) + // findPreference("key_streaming_category"); + // cat.removePreference(p); + } + + if (recstream.equals("vdr-smarttvweb") == false) { + Preference p = findPreference("key_smarttvweb_port"); + p.setEnabled(false); + p = findPreference("key_smarttvweb_recstream_method"); + p.setEnabled(false); + + } + + // create background task + + // start task + + final FetchEditTextPreference macedit = (FetchEditTextPreference) findPreference(getString(R.string.wakeup_wol_mac_key)); + String mac = vdr.getMac(); + if (mac == null) { + mac = ""; + } + macedit.setText(mac); + macedit.setCompoundButtonListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + final String host = vdr.getHost(); + if (host == null) { + Utils.say(VdrPreferencesActivity.this, + getString(R.string.vdr_host_not_defined)); + return; + } + + new VoidAsyncTask() { + + ProgressDialog pd; + + private String mac; + + String message; + + protected void onPreExecute() { + pd = new ProgressDialog(VdrPreferencesActivity.this); + pd.setMessage(getString(R.string.processing)); + pd.show(); + }; + + protected void onPostExecute(Void result) { + pd.dismiss(); + if (message != null) { + Utils.say(VdrPreferencesActivity.this, message); + return; + } + macedit.setEditText(mac); + }; + + @Override + protected Void doInBackground(Void... params) { + try { + String ip = InetAddress.getByName(host) + .getHostAddress(); + ping(ip, vdr.getPort()); + mac = getMacFromArpCache(ip); + } catch (Exception ex) { + message = ex.getLocalizedMessage(); + } + + return null; + } + }.execute(); + } + }); + + final FetchEditTextPreference ipEdit = (FetchEditTextPreference) findPreference(getString(R.string.vdr_host_key)); + String ip = vdr.getHost(); + ipEdit.setText(ip); + ipEdit.setCompoundButtonListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + new AsyncTask<Void, String, List<String>>() { + + ProgressDialog pd; + + String message; + + protected void onPreExecute() { + pd = new ProgressDialog(VdrPreferencesActivity.this); + pd.setMessage(getString(R.string.processing)); + pd.show(); + }; + + protected void onPostExecute(final List<String> ips) { + pd.dismiss(); + if (message != null) { + Utils.say(VdrPreferencesActivity.this, message); + return; + } + + if (ips.isEmpty()) { + Utils.say(VdrPreferencesActivity.this, + R.string.no_results); + return; + } + if (ips.size() == 1) { + ipEdit.setEditText(ips.get(0).toString()); + } else { + new AlertDialog.Builder(VdrPreferencesActivity.this) + .setItems( + ips.toArray(new CharSequence[] {}), + new DialogInterface.OnClickListener() { + @Override + public void onClick( + DialogInterface dialog, + int which) { + String ip = ips.get(which); + ipEdit.setEditText(ip); + } + }).show(); + } + + } + + protected void onProgressUpdate(String... values) { + pd.setMessage(getString(R.string.probing, values[0])); + }; + + @Override + protected List<String> doInBackground(Void... params) { + try { + + final int port = vdr.getPort(); + return DeviceManager.findVDRHosts( + VdrPreferencesActivity.this, port, + new DeviceManager.ProgressListener() { + + @Override + public void publish(String currentIP) { + publishProgress(currentIP); + } + }); + + } catch (Exception ex) { + message = ex.getLocalizedMessage(); + } + return new ArrayList<String>(0); + } + }.execute(); + + } + }); + + updateChildPreferences(); + + findPreference(getString(R.string.timezone_key)) + .setOnPreferenceClickListener(this); + + } + + public void onSharedPreferenceChanged(SharedPreferences arg0, String key) { + hasChanged = true; + updateChildPreferences(); + Preference p = findPreference(key); + updateSummary(p); + + if (key != null && key.equals("key_recstream_method")) { + String recstream = pref.getString("key_recstream_method", + "vdr-live"); + Preference pk = findPreference("key_live_port"); + if (recstream.equals("vdr-live") == false) { + + pk.setEnabled(false); + // PreferenceCategory cat = (PreferenceCategory) + // findPreference("key_streaming_category"); + // cat.removePreference(p); + } else { + pk.setEnabled(true); + } + + if (recstream.equals("vdr-smarttvweb") == false) { + p = findPreference("key_smarttvweb_port"); + p.setEnabled(false); + p = findPreference("key_smarttvweb_recstream_method"); + p.setEnabled(false); + + } else { + p = findPreference("key_smarttvweb_port"); + p.setEnabled(true); + p = findPreference("key_smarttvweb_recstream_method"); + p.setEnabled(true); + + } + + // if(pk) + // cat.addPreference(pk); + // } else { + // cat.removePreference(pk); + // } + } + + Preferences.reloadVDR(this); + } + + @Override + protected void onResume() { + super.onResume(); + pref.registerOnSharedPreferenceChangeListener(this); + } + + @Override + protected void onPause() { + super.onPause(); + // Unregister the listener whenever a key changes + pref.unregisterOnSharedPreferenceChangeListener(this); + } + + private void enableWolPreferences() { + Preference p = findPreference(getString(R.string.wakeup_wol_mac_key)); + if (p != null) + + p.setEnabled(true); + p = findPreference(getString(R.string.wakeup_wol_custom_broadcast_key)); + if (p != null) { + p.setEnabled(true); + } + } + + private void disableWolPreferences() { + Preference p = findPreference(getString(R.string.wakeup_wol_mac_key)); + if (p != null) + p.setEnabled(false); + + p = findPreference(getString(R.string.wakeup_wol_custom_broadcast_key)); + if (p != null) + p.setEnabled(false); + + } + + private void disableWakeupUrlPreferences() { + Preference p = findPreference(getString(R.string.wakeup_url_key)); + if (p != null) { + p.setEnabled(false); + } + p = findPreference(getString(R.string.wakeup_password_key)); + if (p != null) { + p.setEnabled(false); + } + + p = findPreference(getString(R.string.wakeup_user_key)); + if (p != null) { + p.setEnabled(false); + } + } + + private void enableWakeupUrlPrefenreces() { + Preference p = findPreference(getString(R.string.wakeup_url_key)); + if (p != null) { + p.setEnabled(true); + } + + p = findPreference(getString(R.string.wakeup_password_key)); + if (p != null) { + p.setEnabled(true); + } + p = findPreference(getString(R.string.wakeup_user_key)); + if (p != null) { + p.setEnabled(true); + } + } + + private void updateChildPreferences() { + String wakup = pref.getString(getString(R.string.wakeup_method_key), + "wol"); + + if (wakup.equals("url")) { + disableWolPreferences(); + enableWakeupUrlPrefenreces(); + } else {// remote url + disableWakeupUrlPreferences(); + enableWolPreferences(); + } + + for (String key : pref.getAll().keySet()) { + Preference p = findPreference(key); + updateSummary(p); + } + + } + + @Override + public void onBackPressed() { + if (id != -1) {// no new devices + setResult(RESULT_OK); + finish(); + return; + } + if (isNew == true && hasChanged == false) { + // if (pref.commits < 2) {// user has not changed anything + DBAccess.get(this).getVdrDAO().delete(pref.getVdr()); + finish(); + return; + } + super.onBackPressed(); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + + String timezone = vdr.getServerTimeZone(); + + Intent intent = new Intent(this, ZonePicker.class); + intent.putExtra("current_tz", timezone); + startActivityForResult(intent, REQUEST_CODE_PICK_A_TIME_ZONE); + return true; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode != Activity.RESULT_OK) { + super.onActivityResult(requestCode, resultCode, data); + return; + } + + // never mind, onResume also registers this, but the listenres are held + // in a map (as key) so that no double occurance + pref.registerOnSharedPreferenceChangeListener(this); + + if (requestCode == REQUEST_CODE_PICK_A_TIME_ZONE) { + String ntz = data.getStringExtra("new_tz"); + if (ntz != null) { + vdr.setServerTimeZone(ntz); + Editor editor = findPreference("key_timezone").getEditor(); + editor.putString("key_timezone", ntz); + editor.commit(); + // setSummary(ntz, ); + } + } + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/remote/HITK.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/remote/HITK.java new file mode 100644 index 0000000..5d0c596 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/remote/HITK.java @@ -0,0 +1,77 @@ +package de.bjusystems.vdrmanager.remote; + +/** + * Created by lado on 09.05.15. + */ +public enum HITK { + Up, + Down, + Menu, + Ok, + Back, + Left, + Right, + Red, + Green, + Yellow, + Blue, + Zero("0"), + One("1"), + Two("2"), + Three("3"), + Four("4"), + Five("5"), + Six("6"), + Seven("7"), + Eight("8"), + Nine("9"), + Info, + Play_Pause("Play/Pause"), + Play, + Pause, + Stop, + Record, + FastFwd, + FastRew, + Next, + Prev, + Power, + ChannelUp("Channel+"), + ChannelDown("Channel-"), + PrevChannel, + VolumeUp("Volume+"), + VolumeDown("Volume-"), + Mute, + Audio, + Subtitles, + Schedule, + Channels, + Timers, + Recordings, + Setup, + Commands, + User0, + User1, + User2, + User3, + User4, + User5, + User6, + User7, + User8, + User9,; + + String value; + + private HITK() { + this.value = this.name(); + } + + private HITK(String value) { + this.value = value; + } + + public String getValue(){ + return this.value; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/remote/RemoteActivity.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/remote/RemoteActivity.java new file mode 100644 index 0000000..57f76d4 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/remote/RemoteActivity.java @@ -0,0 +1,395 @@ +package de.bjusystems.vdrmanager.remote; + + +import java.util.ArrayList; + +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.view.*; +import de.bjusystems.vdrmanager.gui.Utils; +import org.hampelratte.svdrp.Connection; +import org.hampelratte.svdrp.Response; +import org.hampelratte.svdrp.commands.HITK; + +import de.androvdr.widget.AnimatedTextView; +import de.androvdr.widget.FontAwesome; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.gui.ColoredButton; +import de.bjusystems.vdrmanager.tasks.VoidAsyncTask; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.os.AsyncTask; +import android.os.Bundle; +import android.view.View.OnClickListener; +import android.view.animation.AlphaAnimation; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +public class RemoteActivity extends Activity implements OnClickListener, View.OnLongClickListener { + + + private Connection connection; + + private AnimatedTextView result; + private AlphaAnimation out; + private AlphaAnimation in; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.requestWindowFeature(Window.FEATURE_NO_TITLE); + + //Remove notification bar + this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + View view = getLayoutInflater().inflate(R.layout.remote, null); + + setContentView(view); + + view.setOnLongClickListener(this); + + + ViewGroup viewGroup = (ViewGroup) view.findViewById(R.id.root); + + Button button = (Button) viewGroup.findViewById(R.id.red); + //button.getBackground().setColorFilter(0xFF00FF00, PorterDuff.Mode.MULTIPLY); + + setAllButtonListener(viewGroup); + + result = (AnimatedTextView) findViewById(R.id.result); + //Animation in = AnimationUtils.loadAnimation(this,android.R.anim.fade_in); + //Animation out = AnimationUtils.loadAnimation(this, android.R.anim.fade_out); +/* result.setInAnimation(in); + result.setOutAnimation(out); + result.setFactory(this);*/ + out = new AlphaAnimation(1.0f, 0.0f); + out.setDuration(100); + + in = new AlphaAnimation(0.0f, 1.0f); + in.setDuration(100); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.remote_menu, menu); + return true; + } + + @Override + public void onBackPressed() { + if(isBackKeyRemapped() == false) { + super.onBackPressed(); + } else { + new HitkAsyncTask() { + }.execute("Back"); + + } + } + + + private void resetOverrides(){ + SharedPreferences sharedPref = getSharedPreferences("remote_" + Preferences.get().getCurrentVdr().getId(), Context.MODE_PRIVATE); + sharedPref + .edit() + .clear() + .commit(); + restart(); + } + + private void setBackKeyRemapped(boolean value){ + SharedPreferences sharedPref = getSharedPreferences("misc_" + Preferences.get().getCurrentVdr().getId(), Context.MODE_PRIVATE); + sharedPref// + .edit()// + .putBoolean("backishitk", value)// + .commit(); + } + + private boolean isBackKeyRemapped(){ + SharedPreferences sharedPref = getSharedPreferences("misc_" + Preferences.get().getCurrentVdr().getId(), Context.MODE_PRIVATE); + return sharedPref.getBoolean("backishitk", false); + } + + @Override + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if(isBackKeyRemapped() == false){ + return super.onKeyLongPress(keyCode, event); + } + super.onBackPressed(); + return true; + } + return super.onKeyLongPress(keyCode, event); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + MenuItem item = menu.findItem(R.id.remapback); + item.setChecked(isBackKeyRemapped()); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + switch (item.getItemId()) { + case R.id.reset: { + resetOverrides(); + return true; + } + case R.id.exprt: { + Utils.say(this, R.string.not_yet_implemented); + return true; + } + case R.id.imprt: { + Utils.say(this, R.string.not_yet_implemented); + return true; + } + case R.id.remapback: { + if(item.isChecked()){ + setBackKeyRemapped(false); + } else { + setBackKeyRemapped(true); + Utils.say(this, R.string.remapback_hint); + } + } + } + + return super.onOptionsItemSelected(item); + } + + @Override + protected void onPause() { + super.onPause(); + if (connection != null) { + new VoidAsyncTask() { + @Override + protected Void doInBackground(Void... params) { + try { + connection.close(); + connection = null; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + }.execute(); + + } + } + + + public void setAllButtonListener(ViewGroup viewGroup) { + + // + SharedPreferences sharedPref = getSharedPreferences("remote_" + Preferences.get().getCurrentVdr().getId(), Context.MODE_PRIVATE); + View v; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + v = viewGroup.getChildAt(i); + if (v instanceof ViewGroup) { + setAllButtonListener((ViewGroup) v); + } else if (v instanceof Button) { + if (v.getTag() == null) { + continue; + } + ((Button) v).setOnClickListener(this); + ((Button) v).setOnLongClickListener(this); + + String hitk = sharedPref.getString("key_" + String.valueOf(v.getTag()), null); + setOverrideTag(v, hitk); + String label = sharedPref.getString("label_" + String.valueOf(v.getTag()), null); + setOverrideLabel((Button)v, label); + } + } + } + + private void setOverrideLabel(Button b, CharSequence label) { + if(label == null){ + return; + } + b.setText(label); + } + + + private void restart() { + + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + recreate(); + } else { + Intent intent = getIntent(); + overridePendingTransition(0, 0); + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + finish(); + + overridePendingTransition(0, 0); + startActivity(intent); + } + } + + + class HitkAsyncTask extends AsyncTask<String, Void, Void>{ + + Response send; + + Exception ex; + + @Override + protected void onPostExecute(Void aVoid) { + result.setText(""); + result.fadeIn(); + if (send != null) { + result.setText(String.valueOf(send.getMessage())); + result.fadeOut(); + //Utils.say(getBaseContext(), send.toString()); + } else if (ex != null) { + result.setText(ex.getMessage()); + } + } + + @Override + protected Void doInBackground(String... hitk) { + try { + + if (connection == null) { + connection = new Connection(Preferences.get().getHost(), Preferences.get().getSvdrpPort()); + } + + send = connection.send(new HITK(hitk[0])); + } catch (Exception ex) { + this.ex = ex; + } + + return null; + } + + + + }; + + @Override + public void onClick(final View v) { + new HitkAsyncTask() { + }.execute(getCurrentTag(v)); + } + + @Override + public boolean onLongClick(final View v) { + + + if (v.getId() == R.id.root) { + openOptionsMenu(); + return true; + } + + CharSequence text = ((Button) v).getText(); + final String tag = (String) v.getTag(); + final String override = (String) v.getTag(-100); + String current = override != null ? override : tag; + + View view = getLayoutInflater().inflate(R.layout.edit_remote_key, null); + + + final EditText hitk = (EditText) view.findViewById(R.id.hitk); + TextView hitkLabel = (TextView) view.findViewById(R.id.hitkLabel); + if (v instanceof ColoredButton) { + hitk.setVisibility(View.GONE); + hitkLabel.setVisibility(View.GONE); + } else { + hitk.setVisibility(View.VISIBLE); + hitkLabel.setVisibility(View.VISIBLE); + hitk.setTypeface( + FontAwesome.getFontAwesome(this) + ); + } + hitk.setText(text); + final Spinner hitkspinner = (Spinner) view.findViewById(R.id.hitkSpinner); + + final ArrayList<String> keys = new ArrayList<>(); + for (de.bjusystems.vdrmanager.remote.HITK hk : de.bjusystems.vdrmanager.remote.HITK.values()) { + keys.add(hk.getValue()); + } + ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(this, android.R.layout.simple_spinner_dropdown_item, keys.toArray(new String[]{})); +// Specify the layout to use when the list of choices appears + // hitkspinner.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); +// Apply the adapter to the spinner + hitkspinner.setAdapter(adapter); + int selected = -1; + for (int i = 0; i < keys.size(); ++i) { + String k = keys.get(i); + if (k.equals(current)) { + selected = i; + break; + } + } + hitkspinner.setSelection(selected); + hitkspinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + + } + }); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + CharSequence nhk = hitk.getText(); + ((Button) v).setText(nhk); + String hitk = (String) hitkspinner.getSelectedItem(); + putVdrKey("key_" + tag, hitk); + putVdrKey("label_" + tag, nhk); + setOverrideTag(v, hitk); + + } + }) + .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + } + }).setView(view); + + builder.create().show(); + + + return false; + } + + private void putVdrKey(String key, CharSequence value) { + SharedPreferences sharedPref = getSharedPreferences("remote_" + Preferences.get().getCurrentVdr().getId(), Context.MODE_PRIVATE); + SharedPreferences.Editor edit = sharedPref.edit(); + edit.putString(key, String.valueOf(value)); + edit.commit(); + } + + private String getCurrentTag(View view) { + Object tag = view.getTag(); + if (tag instanceof String == false) { + return null; + } + + Object otag = view.getTag(-100); + if (otag == null) { + return (String) tag; + } + return (String) otag; + } + + private void setOverrideTag(View view, String hitk) { + view.setTag(-100, hitk); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/AsyncProgressTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/AsyncProgressTask.java new file mode 100644 index 0000000..daeb2c7 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/AsyncProgressTask.java @@ -0,0 +1,63 @@ +package de.bjusystems.vdrmanager.tasks; + +import android.app.Activity; +import de.bjusystems.vdrmanager.gui.SvdrpProgressDialog; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpAsyncTask; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpEvent; + +public abstract class AsyncProgressTask<Result> { + + class AsyncProgress extends SvdrpProgressDialog<Result> { + + public AsyncProgress(final Activity activity, + final SvdrpClient<Result> client) { + super(activity, client); + } + + @Override + public void svdrpEvent(final SvdrpEvent event) { + super.svdrpEvent(event); + switch (event) { + case ABORTED: + case CONNECT_ERROR: + case CONNECTION_TIMEOUT: + case ERROR: + case LOGIN_ERROR: + case FINISHED_ABNORMALY: + case FINISHED_SUCCESS: + case CACHE_HIT: + AsyncProgressTask.this.finished(event); + break; + } + } + } + + Activity activity; + SvdrpClient<Result> client; + + public AsyncProgressTask(final Activity activity, + final SvdrpClient<Result> client) { + this.activity = activity; + this.client = client; + } + + public void start() { + + // delete timer + /* + * final SetTimerClient client = new SetTimerClient(timer, true) { + * + * @Override public int getProgressTextId() { return + * R.string.progress_timer_delete; } }; + */ + final SvdrpAsyncTask<Result, SvdrpClient<Result>> task = new SvdrpAsyncTask<Result, SvdrpClient<Result>>( + client); + final AsyncProgress progress = new AsyncProgress(activity, client); + task.addSvdrpListener(progress); + task.addSvdrpExceptionListener(progress); + task.run(); + } + + public abstract void finished(SvdrpEvent event); +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/ChannelsTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/ChannelsTask.java new file mode 100644 index 0000000..1623575 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/ChannelsTask.java @@ -0,0 +1,11 @@ +package de.bjusystems.vdrmanager.tasks; + +import android.app.Activity; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.utils.svdrp.ChannelClient; + +public abstract class ChannelsTask extends AsyncProgressTask<Channel> { + public ChannelsTask(final Activity activity, final ChannelClient client) { + super(activity, client); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/CreateTimerTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/CreateTimerTask.java new file mode 100644 index 0000000..fa7dc43 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/CreateTimerTask.java @@ -0,0 +1,20 @@ +package de.bjusystems.vdrmanager.tasks; + +import android.app.Activity; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.gui.CertificateProblemDialog; +import de.bjusystems.vdrmanager.utils.svdrp.SetTimerClient; +import de.bjusystems.vdrmanager.utils.svdrp.SetTimerClient.TimerOperation; + +public abstract class CreateTimerTask extends AsyncProgressTask<Timer> { + + public CreateTimerTask(final Activity activity, final Timer timer) { + super(activity, new SetTimerClient(timer, TimerOperation.CREATE, new CertificateProblemDialog(activity)) { + @Override + public int getProgressTextId() { + return R.string.progress_timer_save; + } + }); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/DeleteRecordingTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/DeleteRecordingTask.java new file mode 100644 index 0000000..b9f5f75 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/DeleteRecordingTask.java @@ -0,0 +1,18 @@ +package de.bjusystems.vdrmanager.tasks; + +import android.app.Activity; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Recording; +import de.bjusystems.vdrmanager.gui.CertificateProblemDialog; +import de.bjusystems.vdrmanager.utils.svdrp.DelRecordingClient; + +public abstract class DeleteRecordingTask extends AsyncProgressTask<Recording> { + public DeleteRecordingTask(final Activity activity, final Recording r) { + super(activity, new DelRecordingClient(r, new CertificateProblemDialog(activity)) { + @Override + public int getProgressTextId() { + return R.string.progress_recording_delete; + } + }); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/DeleteTimerTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/DeleteTimerTask.java new file mode 100644 index 0000000..b3314d2 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/DeleteTimerTask.java @@ -0,0 +1,20 @@ +package de.bjusystems.vdrmanager.tasks; + +import android.app.Activity; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.gui.CertificateProblemDialog; +import de.bjusystems.vdrmanager.utils.svdrp.SetTimerClient; +import de.bjusystems.vdrmanager.utils.svdrp.SetTimerClient.TimerOperation; + +public abstract class DeleteTimerTask extends AsyncProgressTask<Timer> { + + public DeleteTimerTask(final Activity activity, final Timer timer) { + super(activity, new SetTimerClient(timer, TimerOperation.DELETE, new CertificateProblemDialog(activity)) { + @Override + public int getProgressTextId() { + return R.string.progress_timer_delete; + } + }); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/ModifyTimerTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/ModifyTimerTask.java new file mode 100644 index 0000000..80aae53 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/ModifyTimerTask.java @@ -0,0 +1,24 @@ +package de.bjusystems.vdrmanager.tasks; + +import android.app.Activity; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.gui.CertificateProblemDialog; +import de.bjusystems.vdrmanager.utils.svdrp.SetTimerClient; +import de.bjusystems.vdrmanager.utils.svdrp.SetTimerClient.TimerOperation; + +public abstract class ModifyTimerTask extends AsyncProgressTask<Timer> { + + public ModifyTimerTask(final Activity activity, final Timer newTimer, final Timer oldTimer) { + super(activity, new SetTimerClient(newTimer, oldTimer, TimerOperation.MODIFY, new CertificateProblemDialog(activity)) { + @Override + public int getProgressTextId() { + return R.string.progress_timer_modify; + } + + + }); + + + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/ToggleTimerTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/ToggleTimerTask.java new file mode 100644 index 0000000..82daae4 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/ToggleTimerTask.java @@ -0,0 +1,27 @@ +package de.bjusystems.vdrmanager.tasks; + +import android.app.Activity; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Timer; +import de.bjusystems.vdrmanager.gui.CertificateProblemDialog; +import de.bjusystems.vdrmanager.utils.svdrp.SetTimerClient; +import de.bjusystems.vdrmanager.utils.svdrp.SetTimerClient.TimerOperation; + +public abstract class ToggleTimerTask extends AsyncProgressTask<Timer> { + + public ToggleTimerTask(final Activity activity, final Timer timer) { + super(activity, new SetTimerClient(timer, TimerOperation.TOGGLE, new CertificateProblemDialog(activity)) { + boolean enabled = timer.isEnabled(); + + @Override + public int getProgressTextId() { + if (enabled) { + return R.string.progress_timer_disable; + } else { + return R.string.progress_timer_enable; + } + } + }); + timer.setEnabled(!timer.isEnabled()); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/VoidAsyncTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/VoidAsyncTask.java new file mode 100644 index 0000000..1d9f9f7 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/tasks/VoidAsyncTask.java @@ -0,0 +1,7 @@ +package de.bjusystems.vdrmanager.tasks; + +import android.os.AsyncTask; + +public abstract class VoidAsyncTask extends AsyncTask<Void, Void, Void>{ + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/date/DateFormatter.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/date/DateFormatter.java new file mode 100644 index 0000000..95f99db --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/date/DateFormatter.java @@ -0,0 +1,49 @@ +package de.bjusystems.vdrmanager.utils.date; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +import de.bjusystems.vdrmanager.data.Preferences; + + +/** + * Class for formatting date and time values + * @author bju + * + */ +public class DateFormatter { + + private final String timeString; + private final String dateString; + private final String dailyHeader; + + public DateFormatter(final Date date) { + SimpleDateFormat sdf = new SimpleDateFormat(Preferences.get().getTimeFormat()); + timeString = sdf.format(date); + sdf = new SimpleDateFormat("EEE dd.MM.yy"); + dateString = sdf.format(date); + dailyHeader = DateFormat.getDateInstance(DateFormat.FULL).format(date); + } + + public DateFormatter(final long seconds) { + this(new Date(seconds * 1000)); + } + + public DateFormatter(final Calendar cal) { + this(cal.getTime()); + } + + public String getDateString() { + return dateString; + } + + public String getTimeString() { + return timeString; + } + + public String getDailyHeader() { + return dailyHeader; + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/http/HttpHelper.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/http/HttpHelper.java new file mode 100644 index 0000000..0812f73 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/http/HttpHelper.java @@ -0,0 +1,263 @@ +package de.bjusystems.vdrmanager.utils.http; + + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.zip.GZIPInputStream; + +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.HttpVersion; +import org.apache.http.NameValuePair; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.ResponseHandler; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.conn.scheme.PlainSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.entity.HttpEntityWrapper; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; + +/** + * Apache HttpClient helper class for performing HTTP requests. + * + * This class is intentionally *not* bound to any Android classes so that it is easier + * to develop and test. Use calls to this class inside Android AsyncTask implementations + * (or manual Thread-Handlers) to make HTTP requests asynchronous and not block the UI Thread. + * + * TODO cookies + * TODO multi-part binary data + * TODO follow 302s? + * TODO shutdown connection mgr? - client.getConnectionManager().shutdown(); + * + * @author ccollins + * + */ +public class HttpHelper { + + private static final String CONTENT_TYPE = "Content-Type"; + private static final int POST_TYPE = 1; + private static final int GET_TYPE = 2; + private static final String GZIP = "gzip"; + private static final String ACCEPT_ENCODING = "Accept-Encoding"; + + public static final String MIME_FORM_ENCODED = "application/x-www-form-urlencoded"; + public static final String MIME_TEXT_PLAIN = "text/plain"; + public static final String HTTP_RESPONSE = "HTTP_RESPONSE"; + public static final String HTTP_RESPONSE_ERROR = "HTTP_RESPONSE_ERROR"; + + private final DefaultHttpClient client; + private final ResponseHandler<String> responseHandler; + + /** + * Constructor. + * + */ + public HttpHelper() { + + final HttpParams params = new BasicHttpParams(); + params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); + params.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, HTTP.UTF_8); + params.setParameter(CoreProtocolPNames.USER_AGENT, "Apache-HttpClient/Android"); + params.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 15000); + params.setParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false); + final SchemeRegistry schemeRegistry = new SchemeRegistry(); + schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); + schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443)); + final ThreadSafeClientConnManager cm = new ThreadSafeClientConnManager(params, schemeRegistry); + client = new DefaultHttpClient(cm, params); + + // add gzip decompressor to handle gzipped content in responses + // (default we *do* always send accept encoding gzip header in request) + client.addResponseInterceptor(new HttpResponseInterceptor() { + public void process(final HttpResponse response, final HttpContext context) throws HttpException, IOException { + final HttpEntity entity = response.getEntity(); + final Header contentEncodingHeader = entity.getContentEncoding(); + if (contentEncodingHeader != null) { + final HeaderElement[] codecs = contentEncodingHeader.getElements(); + for (int i = 0; i < codecs.length; i++) { + if (codecs[i].getName().equalsIgnoreCase(HttpHelper.GZIP)) { + response.setEntity(new GzipDecompressingEntity(response.getEntity())); + return; + } + } + } + } + }); + + responseHandler = new BasicResponseHandler(); + } + + /** + * Perform a simple HTTP GET operation. + * + */ + public String performGet(final String url) { + return performRequest(null, url, null, null, null, null, HttpHelper.GET_TYPE); + } + + /** + * Perform an HTTP GET operation with user/pass and headers. + * + */ + public String performGet(final String url, final String user, final String pass, + final Map<String, String> additionalHeaders) { + return performRequest(null, url, user, pass, additionalHeaders, null, HttpHelper.GET_TYPE); + } + + /** + * Perform a simplified HTTP POST operation. + * + */ + public String performPost(final String url, final Map<String, String> params) { + return performRequest(HttpHelper.MIME_FORM_ENCODED, url, null, null, null, params, HttpHelper.POST_TYPE); + } + + /** + * Perform an HTTP POST operation with user/pass, headers, request + parameters, + * and a default content-type of "application/x-www-form-urlencoded." + * + */ + public String performPost(final String url, final String user, final String pass, + final Map<String, String> additionalHeaders, final Map<String, String> params) { + return performRequest(HttpHelper.MIME_FORM_ENCODED, url, user, pass, additionalHeaders, params, + HttpHelper.POST_TYPE); + } + + /** + * Perform an HTTP POST operation with flexible parameters (the + complicated/flexible version of the method). + * + */ + public String performPost(final String contentType, final String url, final String user, final String pass, + final Map<String, String> additionalHeaders, final Map<String, String> params) { + return performRequest(contentType, url, user, pass, additionalHeaders, params, HttpHelper.POST_TYPE); + } + + // + // private methods + // + private String performRequest(final String contentType, final String url, final String user, final String pass, + final Map<String, String> headers, final Map<String, String> params, final int requestType) { + + // add user and pass to client credentials if present + if ((user != null) && (pass != null)) { + client.getCredentialsProvider().setCredentials(AuthScope.ANY, + new UsernamePasswordCredentials(user, pass)); + } + + // process headers using request interceptor + final Map<String, String> sendHeaders = new HashMap<String, String>(); + // add encoding header for gzip if not present + if (!sendHeaders.containsKey(HttpHelper.ACCEPT_ENCODING)) { + sendHeaders.put(HttpHelper.ACCEPT_ENCODING, HttpHelper.GZIP); + } + if ((headers != null) && (headers.size() > 0)) { + sendHeaders.putAll(headers); + } + if (requestType == HttpHelper.POST_TYPE) { + sendHeaders.put(HttpHelper.CONTENT_TYPE, contentType); + } + if (sendHeaders.size() > 0) { + client.addRequestInterceptor(new HttpRequestInterceptor() { + public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { + for (final String key : sendHeaders.keySet()) { + if (!request.containsHeader(key)) { + request.addHeader(key, sendHeaders.get(key)); + } + } + } + }); + } + + // handle POST or GET request respectively + HttpRequestBase method = null; + if (requestType == HttpHelper.POST_TYPE) { + method = new HttpPost(url); + // data - name/value params + List<NameValuePair> nvps = null; + if ((params != null) && (params.size() > 0)) { + nvps = new ArrayList<NameValuePair>(); + for (final Map.Entry<String, String> entry : params.entrySet()) { + nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); + } + } + if (nvps != null) { + try { + final HttpPost methodPost = (HttpPost) method; + methodPost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException("Error peforming HTTP request: " + e.getMessage(), e); + } + } + } else if (requestType == HttpHelper.GET_TYPE) { + method = new HttpGet(url); + } + + + // execute request + return execute(method); + } + + private synchronized String execute(final HttpRequestBase method) { + String response = null; + // execute method returns?!? (rather than async) - do it here sync, and wrap async elsewhere + try { + response = client.execute(method, responseHandler); + } catch (final ClientProtocolException e) { + response = HttpHelper.HTTP_RESPONSE_ERROR + " - " + e.getClass().getSimpleName() + " " + e.getMessage(); + //e.printStackTrace(); + } catch (final IOException e) { + response = HttpHelper.HTTP_RESPONSE_ERROR + " - " + e.getClass().getSimpleName() + " " + e.getMessage(); + //e.printStackTrace(); + } + return response; + } + + static class GzipDecompressingEntity extends HttpEntityWrapper { + public GzipDecompressingEntity(final HttpEntity entity) { + super(entity); + } + + @Override + public InputStream getContent() throws IOException, IllegalStateException { + // the wrapped entity's getContent() decides about repeatability + final InputStream wrappedin = wrappedEntity.getContent(); + return new GZIPInputStream(wrappedin); + } + + @Override + public long getContentLength() { + // length of ungzipped content is not known + return -1; + } + } +} + diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/AliveClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/AliveClient.java new file mode 100644 index 0000000..0d5dca9 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/AliveClient.java @@ -0,0 +1,47 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import de.bjusystems.vdrmanager.data.AliveState; + +/** + * Class for retrieving informations about the running program + * @author bju + * + */ +public class AliveClient extends SvdrpClient<AliveState> { + + /** + * Constructor + * @param certificateProblemListener + */ + public AliveClient(final CertificateProblemListener certificateProblemListener) { + super(certificateProblemListener); + } + + /** + * Starts the EPG request + * @param parameter parameter for lste + */ + @Override + public void run() { + runCommand("aliv"); + } + + @Override + public AliveState parseAnswer(final String line) { + + if (line.startsWith("200")) { + return AliveState.ALIVE; + } + if (line.startsWith("400")) { + return AliveState.DEAD; + } + return AliveState.UNKNOWN; + } + + @Override + public int getProgressTextId() { + return 0; + } + + +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/CertificateProblemListener.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/CertificateProblemListener.java new file mode 100644 index 0000000..ac0ac1e --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/CertificateProblemListener.java @@ -0,0 +1,39 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import java.security.cert.X509Certificate; + +import android.app.Activity; + +/** + * Interface for reporting problems with the SSL certificate + * @author bju + * + */ +public interface CertificateProblemListener { + + /** + * Possible user decisions on certificate problems + */ + public enum CertificateProblemAction { + + /** Abort the connection */ + ABORT, + /** Accept the certificate this time */ + ACCEPT_ONCE, + /** Accept the certificate forever */ + ACCEPT_FOREVER + } + + /** + * Reports the certificate problem and waits for a user decision + * @param chain Certificate trust chain + * @param authType authentication type + */ + CertificateProblemAction reportProblem(final X509Certificate[] chain, final String authType); + + /** + * Gets the current activity + * @return activity + */ + Activity getCurrentActivity(); +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/ChannelClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/ChannelClient.java new file mode 100644 index 0000000..c3761c4 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/ChannelClient.java @@ -0,0 +1,184 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.Preferences; + +/** + * Class for retrieving informations about the running program + * + * @author bju + * + */ +public class ChannelClient extends SvdrpClient<Channel> implements +SvdrpListener, SvdrpResultListener<Channel> { + + private static final ArrayList<String> channelGroups = new ArrayList<String>(); + + private static final ArrayList<String> channelSources = new ArrayList<String>(); + + private static LinkedHashMap<String, ArrayList<Channel>> groupChannels = new LinkedHashMap<String, ArrayList<Channel>>(); + + private static TreeMap<String, ArrayList<Channel>> providerChannels = new TreeMap<String, ArrayList<Channel>>(); + + private static TreeMap<String, ArrayList<Channel>> sourceChannels = new TreeMap<String, ArrayList<Channel>>(); + + private static ArrayList<Channel> channels = new ArrayList<Channel>(); + + private static Map<String, Channel> idChannels = new HashMap<String, Channel>(); + + public static Map<String, Channel> getIdChannels() { + return idChannels; + } + + private static boolean inited = false; + + public ChannelClient(final CertificateProblemListener certificateProblemListener) { + super(certificateProblemListener); + // if (useCache == false) { + // clearCache(); + // } + addSvdrpListener(this); + addSvdrpResultListener(this); + } + + public static void clearCache() { + channelSources.clear(); + sourceChannels.clear(); + channelGroups.clear(); + groupChannels.clear(); + providerChannels.clear(); + channels.clear(); + idChannels.clear(); + inited = false; + } + + public static ArrayList<String> getChannelGroups() { + return channelGroups; + } + + public static ArrayList<String> getChannelSources() { + return channelSources; + } + + + public static HashMap<String, ArrayList<Channel>> getGroupChannels() { + return groupChannels; + } + + public static TreeMap<String, ArrayList<Channel>> getProviderChannels() { + return providerChannels; + } + + public static TreeMap<String, ArrayList<Channel>> getSourceChannels() { + return sourceChannels; + } + + public static ArrayList<Channel> getChannels() { + return channels; + } + + /** + * Constructor + * + * @param host + * host + * @param port + * port + * @param ssl + * use ssl + */ + // public ChannelClient() { + // this(true); + // + // } + + /** + * Starts the EPG request + * + * @param parameter + * parameter for lste + */ + @Override + synchronized public void run() { + if (inited == true) { + informListener(SvdrpEvent.CACHE_HIT); + } else { + runCommand("channels " + Preferences.get().getChannels()); + } + } + + @Override + public Channel parseAnswer(final String line) { + return new Channel(line); + } + + @Override + public int getProgressTextId() { + return R.string.progress_channels_loading; + } + + ArrayList<Channel> currentChannels = new ArrayList<Channel>(); + + String currentGroup; + + private void addSourceChannel(final Channel c){ + ArrayList<Channel> channels = sourceChannels.get(c.getSource()); + + if(channels == null){ + channels = new ArrayList<Channel>(); + sourceChannels.put(c.getSource(), channels); + channelSources.add(c.getSource()); + } + channels.add(c); + } + + private void received(final Channel c) { + + if (c.isGroupSeparator()) { + currentGroup = c.getName(); + channelGroups.add(currentGroup); + currentChannels = new ArrayList<Channel>(); + groupChannels.put(c.getName(), currentChannels); + } else { + + addSourceChannel(c); + + if (channelGroups.isEmpty()) {// receiver channel with no previous + // group + channelGroups.add(""); + groupChannels.put("", currentChannels); + } + + c.setGroup(currentGroup); + channels.add(c); + idChannels.put(c.getId(), c); + currentChannels.add(c); + final String provider = c.getProvider(); + ArrayList<Channel> pchannels = providerChannels.get(provider); + if (pchannels == null) { + pchannels = new ArrayList<Channel>(); + providerChannels.put(provider, pchannels); + } + pchannels.add(c); + } + } + + @Override + public void svdrpEvent(final Channel c) { + received(c); + } + + @Override + public void svdrpEvent(final SvdrpEvent event) { + if (event == SvdrpEvent.FINISHED_SUCCESS) { + inited = true; + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/DelRecordingClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/DelRecordingClient.java new file mode 100644 index 0000000..522e285 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/DelRecordingClient.java @@ -0,0 +1,49 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Recording; + +/** + * Class for deleting a record + * @author lado + * + */ +public class DelRecordingClient extends SvdrpClient<Recording> { + + /** current recording */ + Recording recording; + + /** + * Constructor + * Recording + */ + public DelRecordingClient(final Recording recording, final CertificateProblemListener certificateProblemListener) { + super(certificateProblemListener); + this.recording = recording; + } + + /** + * Starts the request + */ + @Override + public void run() { + + final StringBuilder command = new StringBuilder(); + + command.append("drecording "); + command.append(recording.toCommandLine()); + runCommand(command.toString()); + } + + + @Override + public int getProgressTextId() { + return R.string.progress_timer_save; + } + + @Override + protected Recording parseAnswer(final String line) { + return null; + } +} + diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/EpgClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/EpgClient.java new file mode 100644 index 0000000..a3d5b7e --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/EpgClient.java @@ -0,0 +1,92 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.Epg; +import de.bjusystems.vdrmanager.data.EpgSearchParams; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.data.Timer; + +/** + * Class for retrieving informations about the running program + * @author bju + * + */ +public class EpgClient extends SvdrpClient<Epg> { + + /** Time to retrieve EPG for */ + private String time; + /** Channel to retrieve EPG for */ + private Channel channel; + /** Search parameters to retrieve EPG for */ + private EpgSearchParams search; + /** Last read EPG */ + private Epg lastEpg; + + /** + * Constructor + */ + private EpgClient(final CertificateProblemListener certificateProblemListener) { + super(certificateProblemListener); + this.time = null; + this.channel = null; + this.search = null; + } + + /** + * Constructor + * @param time time to search for epg events + */ + public EpgClient(final String time, final CertificateProblemListener certificateProblemListener) { + this(certificateProblemListener); + this.time = time; + } + + /** + * Constructor + * @param channel channel to search for epg events + */ + public EpgClient(final Channel channel, final CertificateProblemListener certificateProblemListener) { + this(certificateProblemListener); + this.channel = channel; + } + + public EpgClient(final EpgSearchParams search, final CertificateProblemListener certificateProblemListener) { + this(certificateProblemListener); + this.search = search; + } + + /** + * Starts the EPG request + * @param parameter parameter for lste + */ + @Override + public void run() { + if (time != null) { + runCommand(String.format("tevents %s %s", time, Preferences.getPreferences().getChannels())); + } else if (channel != null) { + runCommand(String.format("cevents %s", channel.getNumber())); + } else if (search != null) { + runCommand(String.format("search %s:%s", Preferences.get().getChannels(), search.toCommandLine())); + } + } + + @Override + public Epg parseAnswer(final String line) { + + if (line.startsWith("E")) { + lastEpg = new Epg(line); + return lastEpg; + } else if (line.startsWith("T")) { + lastEpg.setTimer(new Timer(line)); + } + return null; + } + + @Override + public int getProgressTextId() { + return R.string.progress_whatson_loading; + } + +} + diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/MySSLSocketFactory.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/MySSLSocketFactory.java new file mode 100644 index 0000000..6c87faa --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/MySSLSocketFactory.java @@ -0,0 +1,253 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import de.bjusystems.vdrmanager.app.VdrManagerApp; + + +/** + * SSLSocketFactory + * @author bju + */ +public class MySSLSocketFactory extends org.apache.http.conn.ssl.SSLSocketFactory { + + /** The key store file */ + private final String keyStoreFile = "KeyStore"; + + /** the key store */ + private KeyStore appKeyStore; + + /** the real socket factory */ + private final SSLSocketFactory sslFactory; + + /** the trust managers */ + private X509TrustManager[] trustManagers; + + /** the current activity */ + private final Activity activity; + + public MySSLSocketFactory(final boolean acceptAllCertificates, final CertificateProblemListener certProblemListener) + throws KeyManagementException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException { + + super(null); + + // save context + this.activity = certProblemListener.getCurrentActivity(); + + // accept all host names + this.setHostnameVerifier(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + + // load the key store + initKeyStore(); + + // initialize the trust managers + if (acceptAllCertificates) { + initInsecureTrustManagers(); + } else { + initSecureTrustManagers(certProblemListener); + } + + // create SSL context + final SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, trustManagers, new SecureRandom()); + + // create the real factory + sslFactory = context.getSocketFactory(); + + } + + @Override + public Socket createSocket() throws IOException { + return sslFactory.createSocket(); + } + + /** + * initialize the key store + * @return KeyStore + * @throws KeyStoreException + */ + private void initKeyStore() throws KeyStoreException { + + try { + appKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + + try { + final InputStream stream = activity.openFileInput(keyStoreFile); + appKeyStore.load(stream, null); + } catch (final FileNotFoundException e) { + appKeyStore.load(null); + } + } catch (final Exception e) { + throw new KeyStoreException(e); + } + } + + /** + * initialize the trust managers validating certificates + * @param acceptAllCertificates accept all certificates + * @param certProblemListener listener to inform about certificate problems + * @throws NoSuchAlgorithmException + * @throws KeyStoreException + */ + private void initSecureTrustManagers(final CertificateProblemListener certProblemListener) throws NoSuchAlgorithmException, KeyStoreException { + + final List<X509TrustManager> trustManagerList = new ArrayList<X509TrustManager>(); + + // initialize the trust manager accepting certificates contained in the session key store + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); + trustManagerFactory.init(((VdrManagerApp)activity.getApplication()).getSessionKeyStore()); + X509TrustManager trustManager = getTrustManager(trustManagerFactory); + if (trustManager != null) { + trustManagerList.add(trustManager); + } + + // initialize the trust manager accepting certificates contained in the permanent key store + trustManagerFactory = TrustManagerFactory.getInstance("X509"); + trustManagerFactory.init(appKeyStore); + trustManager = getTrustManager(trustManagerFactory); + if (trustManager != null) { + trustManagerList.add(trustManager); + } + + // initialize the trust manager accepting certificates accepted from the system + trustManagerFactory = TrustManagerFactory.getInstance("X509"); + trustManagerFactory.init((KeyStore)null); + trustManager = getTrustManager(trustManagerFactory); + if (trustManager != null) { + trustManagerList.add(trustManager); + } + + trustManagers = new X509TrustManager[] { + new X509TrustManager() { + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + CertificateException lastException = null; + for(final X509TrustManager tm : trustManagerList) { + try { + tm.checkServerTrusted(chain, authType); + return; + } catch (final CertificateException e) { + lastException = e; + } + } + + switch (certProblemListener.reportProblem(chain, authType)) { + case ACCEPT_ONCE: + saveCertificate(chain, authType, false); + return; + case ACCEPT_FOREVER: + saveCertificate(chain, authType, true); + return; + default: + if (lastException != null) { + throw lastException; + } + break; + } + + + throw new CertificateException("Certificate not validated"); + } + + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + // NOP + } + } + }; + } + + /** + * initializes the trust managers validating nothing + */ + private void initInsecureTrustManagers() { + + trustManagers = new X509TrustManager[] { + new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + } + + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException { + } + } + }; + } + + /** + * finds the X509 trust manager + * @param trustManagerFactory TrustManager factory + * @return X509 trust manager + */ + private X509TrustManager getTrustManager(final TrustManagerFactory trustManagerFactory) { + + for(final TrustManager trustManager : trustManagerFactory.getTrustManagers()) { + if (trustManager instanceof X509TrustManager) { + return (X509TrustManager) trustManager; + } + } + return null; + } + + /** + * Saves the certificate + * @param chain certificate chain + * @param authType authentication type + */ + private void saveCertificate(final X509Certificate[] chain, final String authType, final boolean permanently) { + + // get the certificate alias + final String alias = chain[0].getSubjectDN().toString(); + + // key store to use + final KeyStore saveKeyStore = permanently ? appKeyStore : ((VdrManagerApp)activity.getApplication()).getSessionKeyStore(); + + // store the certificate for this alias + try { + saveKeyStore.setCertificateEntry(alias, chain[0]); + + // the session key store is not saved + if (permanently) { + final FileOutputStream stream = activity.openFileOutput(keyStoreFile, Context.MODE_PRIVATE); + saveKeyStore.store(stream, null); + } + } catch (final Exception e) { + Log.e(getClass().getName(), "Can't save certificate for ' " + alias + "' as trusted"); + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/RecordingClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/RecordingClient.java new file mode 100644 index 0000000..f201ed9 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/RecordingClient.java @@ -0,0 +1,31 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Recording; + +public class RecordingClient extends SvdrpClient<Recording> { + + /** + * Constructor + * @param certificateProblemListener + */ + public RecordingClient(final CertificateProblemListener certificateProblemListener) { + super(certificateProblemListener); + } + + @Override + protected Recording parseAnswer(final String line) { + return new Recording(line); + } + + @Override + public int getProgressTextId() { + return R.string.progress_recordings_loading; + } + + @Override + public synchronized void run() { + runCommand("recordings"); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SetTimerClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SetTimerClient.java new file mode 100644 index 0000000..8daab75 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SetTimerClient.java @@ -0,0 +1,90 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Timer; + +/** + * Class for retrieving informations about the running program + * @author bju + * + */ +public class SetTimerClient extends SvdrpClient<Timer> { + + public enum TimerOperation { + CREATE("C"),// + DELETE("D"),// + MODIFY("M"),// + TOGGLE("T"),// + ; + private String command; + private TimerOperation(final String command){ + this.command = command; + } + public String getCommand(){ + return this.command; + } + + } + + /** channel names for timer */ + Timer newTimer; + + Timer oldTimer; + + /** timer should be deleted */ + private final TimerOperation timerOperation; + + /** + * @param newTimer Das was modifiziert angelegt wird + */ + public SetTimerClient(final Timer newTimer, final TimerOperation op, final CertificateProblemListener certificateProblemListener) { + this(newTimer, null, op, certificateProblemListener); + } + + /** + * @param newTimer + * @param oldTimer this is original Timer, if any (modify) + * @param op + */ + public SetTimerClient(final Timer newTimer, final Timer oldTimer, final TimerOperation op, final CertificateProblemListener certificateProblemListener) { + super(certificateProblemListener); + this.newTimer = newTimer; + this.oldTimer = oldTimer; + this.timerOperation = op; + } + + + /** + * Starts the request + */ + @Override + public void run() { + + final StringBuilder command = new StringBuilder(); + + command.append("timer "); + command.append(timerOperation.getCommand()); + //command.append(oldTimer.getNumber()); + command.append(" "); + command.append(newTimer.toCommandLine()); + if(timerOperation == TimerOperation.MODIFY){ + command.append("#|#|#").append(oldTimer.toCommandLine()); + } + //timer D 1:1:2011-11-11:1513:1710:50:99:Mirrors 2 + //timer C 1:1:2011-11-11:2223:2250:50:99:Zapping + //timer T 0:1:2011-11-11:2013:2230:50:99:So spielt das Leben + //timer M + runCommand(command.toString()); + } + + @Override + public Timer parseAnswer(final String line) { + return new Timer(line); + } + + @Override + public int getProgressTextId() { + return R.string.progress_timer_save; + } +} + diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpAsyncTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpAsyncTask.java new file mode 100644 index 0000000..505b084 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpAsyncTask.java @@ -0,0 +1,165 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import java.util.ArrayList; +import java.util.List; + +import android.os.AsyncTask; + +public class SvdrpAsyncTask<Result, Client extends SvdrpClient<Result>> extends +AsyncTask<Void, Object, Void> implements SvdrpListener, +SvdrpExceptionListener, SvdrpResultListener<Result> { + + Client client; + + Throwable ex; + + SvdrpEvent event; + + List<SvdrpListener> eventListeners = new ArrayList<SvdrpListener>(); + + List<SvdrpExceptionListener> exceptionListeners = new ArrayList<SvdrpExceptionListener>(); + + List<SvdrpFinishedListener<Result>> finishedListeners = new ArrayList<SvdrpFinishedListener<Result>>(); + + //CertificateProblemListener certificateProblemListener = null; + + //CertificateProblemAction certificateProblemAction; + + public SvdrpAsyncTask(final Client client) { + this.client = client; + this.client.addSvdrpListener(this); + this.client.addSvdrpExceptionListener(this); + this.client.addSvdrpResultListener(this); + } + + protected List<Result> results = new ArrayList<Result>(); + + + public List<Result> getResults() { + return results; + } + + + /** + * Adds the listener to the list of listeners + * + * @param listener + * listener + */ + public void addSvdrpListener(final SvdrpListener listener) { + // client.addSvdrpListener(listener); + eventListeners.add(listener); + } + + /** + * Adds the listener to the list of listeners + * + * @param listener + * listener + */ + public void addSvdrpResultListener( + final SvdrpResultListener<Result> listener) { + client.addSvdrpResultListener(listener); + } + + public void addSvdrpFinishedListener(final SvdrpFinishedListener<Result> liste) { + finishedListeners.add(liste); + } + + /** + * Adds the listener to the list of listeners + * + * @param listener + * listener + */ + public void addSvdrpExceptionListener(final SvdrpExceptionListener listener) { + // client.addSvdrpExceptionListener(listener); + exceptionListeners.add(listener); + } + + public void run() { + execute(); + } + + @Override + protected Void doInBackground(final Void... params) { + client.run(); + return null; + } + + @Override + protected void onProgressUpdate(final Object... values) { + + if (values.length == 1) { + + if (List.class.isAssignableFrom(values[0].getClass())) { + for (final SvdrpFinishedListener<Result> listener : finishedListeners) { + listener.finished((List<Result>) values[0]); + } + return; + } + + for (final SvdrpListener listener : eventListeners) { + listener.svdrpEvent((SvdrpEvent) values[0]); + } + + } else if (values.length == 2) { + for (final SvdrpExceptionListener listener : exceptionListeners) { + listener.svdrpEvent((SvdrpEvent) values[0], (Throwable) values[1]); + } + } + + /* + * switch (event) { case CONNECTING: { + * setMessage(R.string.progress_connect); progress.show(); break; } + * + * case LOGGED_IN: { setMessage(R.string.progress_login); break; } + * + * case COMMAND_SENT: { setMessage(client.getProgressTextId()); break; } + * + * case DISCONNECTING: { setMessage(R.string.progress_disconnect); + * break; } + * + * case ERROR: case CONNECTION_TIMEOUT: case CONNECT_ERROR: case + * FINISHED_ABNORMALY: case CACHE_HIT: case FINISHED_SUCCESS: case + * LOGIN_ERROR: { progress.dismiss(); } + * + * } + */ + } + + // @Override + // protected void onPostExecute(SvdrpException exception) { + // for (SvdrpExceptionListener l : exceptionListeners) { + // l.svdrpEvent(exception.getEvent(), ex); + // } + // } + + @Override + public void svdrpEvent(final SvdrpEvent event) { + publishProgress(event);; + if(event == SvdrpEvent.FINISHED_SUCCESS){ + publishProgress(results); + } + } + + // @Override + // public void finished(ListResult> results) { + // publishProgress(results); + // } + + @Override + public void svdrpEvent(final SvdrpEvent event, final Throwable t) { + publishProgress(event, t); + } + + + @Override + public void svdrpEvent(final Result result) { + results.add(result); + } + + public void abort(){ + client.abort(); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpClient.java new file mode 100644 index 0000000..0198bb0 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpClient.java @@ -0,0 +1,533 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketTimeoutException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Timer; +import java.util.TimerTask; +import java.util.zip.GZIPInputStream; +import java.util.zip.InflaterInputStream; + +import android.sax.StartElementListener; +import android.util.Log; +import de.bjusystems.vdrmanager.app.C; +import de.bjusystems.vdrmanager.data.Preferences; + +/** + * Class for SVDRP communication + * + * @author bju + * + */ +public abstract class SvdrpClient<Result> { + + private final String TAG = getClass().getName(); + + /** Socket for connection to SVDRP */ + private Socket socket; + /** Output stream for sending commands */ + private OutputStream outputStream; + /** Input stream for reading answer lines */ + private InputStream inputStream; + /** flag for stopping the current request */ + private boolean abort; + /** listener for events */ + private final List<SvdrpListener> svdrpListeners = new ArrayList<SvdrpListener>(); + /** Listener for start */ + private final List<SvdrpStartListener> startListeners = new ArrayList<SvdrpStartListener>(); + /** listeners for results */ + private final List<SvdrpResultListener<Result>> svdrpResultListeners = new ArrayList<SvdrpResultListener<Result>>(); + /** listeners for exceptions */ + private final List<SvdrpExceptionListener> svdrpExceptionListeners = new ArrayList<SvdrpExceptionListener>(); + /** listeners for finished job */ + private final List<SvdrpFinishedListener<Result>> svdrpFinishedListeners = new ArrayList<SvdrpFinishedListener<Result>>(); + /** listener for certificate problems set by caller */ + private final CertificateProblemListener certificateProblemListener; + + /** list of results */ + // private final List<Result> results = new ArrayList<Result>(); + /** should the listener be informed about each received result */ + // private boolean resultInfoEnabled = false; + /** + * @return true if the client has result + */ + // public boolean hasResults(){ + // return results.isEmpty() == false; + // } + + private final Timer watchDog = new Timer(); + + private String encoding; + + + // private NativeDES crypt = new NativeDES(); + + public boolean isConnected() { + if (socket == null) { + return false; + } + return socket.isConnected(); + } + + /** + * Parse received answer line + * + * @param line + * line + * @return received data object or null if not completed yet + */ + protected abstract Result parseAnswer(String line); + + public abstract int getProgressTextId(); + + public abstract void run(); + + /** + * Constructor + * + * @param prefs + * Preferences + */ + protected SvdrpClient( + final CertificateProblemListener certificateProblemListener) { + // results.clear(); + this.certificateProblemListener = certificateProblemListener; + encoding = Preferences.get().getEncoding(); + } + + /** + * Remove all listeners + */ + public void clearListener() { + svdrpExceptionListeners.clear(); + svdrpListeners.clear(); + svdrpResultListeners.clear(); + } + + /** + * Adds the listener to the list of listeners + * + * @param listener + * listener + */ + public void addSvdrpListener(final SvdrpListener listener) { + svdrpListeners.add(listener); + } + + public void addStartListener(final SvdrpStartListener listener) { + startListeners.add(listener); + } + + /** + * Adds the listener to the list of listeners + * + * @param listener + * listener + */ + public void addSvdrpResultListener( + final SvdrpResultListener<Result> listener) { + svdrpResultListeners.add(listener); + } + + /** + * Adds the listener to the list of listeners + * + * @param listener + * listener + */ + public void addSvdrpFinishedListener( + final SvdrpFinishedListener<Result> listener) { + svdrpFinishedListeners.add(listener); + } + + /** + * Adds the listener to the list of listeners + * + * @param listener + * listener + */ + public void addSvdrpExceptionListener(final SvdrpExceptionListener listener) { + svdrpExceptionListeners.add(listener); + } + + /** + * Removes the listener from the list of listeners + * + * @param listener + * listener + */ + public void removeSvdrpListener(final SvdrpListener listener) { + svdrpListeners.remove(listener); + } + + public void remoeStartListener(final SvdrpStartListener listener) { + startListeners.remove(listener); + } + + public void removeSvdrpResultListener( + final SvdrpResultListener<Result> listener) { + svdrpResultListeners.remove(listener); + } + + public void removeSvdrpExceptionListener( + final SvdrpExceptionListener listener) { + svdrpExceptionListeners.remove(listener); + } + + /** + * Cancel the current request + */ + public void abort() { + abort = true; + try { + if (isConnected()) { + socket.shutdownInput(); + socket.shutdownOutput(); + socket.close(); + } + } catch (final Exception ex) { + Log.w(TAG, ex); + } + + } + + // /** + // * Gets the list of results + // * + // * @return results + // */ + // public List<Result> getResults() { + // return results; + // } + + /** + * Connect to SVDRP + * + * @param host + * host + * @param port + * port + * @param ssl + * use SSL + * @throws IOException + * on errors + */ + protected boolean connect() throws IOException { + + final Preferences prefs = Preferences.get(); + try { + // connect + informListener(SvdrpEvent.CONNECTING); + + if (Preferences.get().isSecure()) { + socket = new MySSLSocketFactory(false, + certificateProblemListener).createSocket(); + } else { + socket = new Socket(); + } + + socket.connect( + new InetSocketAddress(prefs.getHost(), prefs + .getPort()), + prefs.getConnectionTimeout() * 1000);// 8 secs for connect + if (abort) { + informListener(SvdrpEvent.ABORTED); + } + // + socket.setSoTimeout(prefs.getReadTimeout() * 1000);// 15 sec for + // each read + final long delay = C.ONE_MINUTE_IN_MILLIS * prefs.getTimeout() * 60; // in + // 3 + // minutes + // we + // abort + // the + // communication + watchDog.schedule(new TimerTask() { + @Override + public void run() { + Log.w(TAG, "Aborted after " + delay + " ms"); + abort = true; + } + }, delay); + informListener(SvdrpEvent.CONNECTED); + + // create streams + outputStream = socket.getOutputStream(); + inputStream = socket.getInputStream(); + + } catch (final SocketTimeoutException sote) { + Log.w(TAG, sote); + if (abort) { + informListener(SvdrpEvent.ABORTED); + } else { + informListener(SvdrpEvent.CONNECTION_TIMEOUT); + } + return false; + } catch (final Exception e) { + + Log.w(TAG, e); + if (abort) { + informListener(SvdrpEvent.ABORTED); + } else { + informListener(SvdrpEvent.CONNECT_ERROR); + } + return false; + } + + // password needed? + informListener(SvdrpEvent.LOGIN); + writeLine("passwd " + prefs.getPassword()); + if (!readLine().startsWith("!OK")) { + informListener(SvdrpEvent.LOGIN_ERROR); + disconnect(); + return false; + } else { + informListener(SvdrpEvent.LOGGED_IN); + } + return true; + } + + /** + * Disconnect from SVDRP if connected + * + * @throws IOException + * on errors + */ + protected void disconnect() throws IOException { + informListener(SvdrpEvent.DISCONNECTING); + if (socket != null && socket.isConnected()) { + writeLine("quit"); + socket.close(); + socket = null; + } + informListener(SvdrpEvent.DISCONNECTED); + } + + /** + * Sends one line to SVDRP + * + * @param line + * line of text + * @throws IOException + * on errors + */ + protected void writeLine(final String line) throws IOException { + + final String command = line + "\r\n"; + // if (false && Preferences.get().isSecure()) { + // command = crypt.encrypt(command, Preferences.get().getPassword()); + // } + final byte[] bytes = command.getBytes("utf-8"); + outputStream.write(bytes); + outputStream.flush(); + } + + /** + * Reads one line from SVDRP + * + * @return line read + * @throws IOException + * on errors + */ + protected String readLine() throws IOException { + + // handle not gzipped input + final ByteArrayOutputStream lineBytes = new ByteArrayOutputStream(); + + for (;;) { + + // read next char + final int d = inputStream.read(); + if (d < 0) { + break; + } + final char c = (char) d; + + // skip '\r' + if (c == '\r') { + continue; + } + + // with '\n' the line is completed + if (c == '\n') { + break; + } + + // remember char + lineBytes.write(c); + } + + String line = null; + try { + line = lineBytes.toString(encoding); + lineBytes.close(); + } catch (final UnsupportedEncodingException usex) { + Log.w(TAG, usex); + line = lineBytes.toString(); + } + // if (false && Preferences.get().isSecure()) { + // line = crypt.decrypt(line, Preferences.get().getPassword()); + // } + return line; + } + + public void runCommand(final String command) { + + try { + + // reset cancel flag + abort = false; + + // clear results + // results.clear(); + + // connect + final boolean connected = connect(); + if (!connected) { + return; + } + + // activate compression + Log.i(TAG, "Activate compression"); + writeLine("compress"); + + // send command + informListener(SvdrpEvent.COMMAND_SENDING); + writeLine(command); + informListener(SvdrpEvent.COMMAND_SENT); + Log.i(TAG, SvdrpEvent.COMMAND_SENT + ":" + command); + + // get the answer for the compress command or + // the first line of the answer for the command + String line = readLine(); + if (line.startsWith("!OK")) { + final String[] words = line.split(" "); + if (words.length > 1) { + final String mode = words[1].toUpperCase(Locale.getDefault()); + if (mode.equals("ZLIB")) { + Log.i(TAG, "ZLIB compression activated"); + inputStream = new InflaterInputStream(inputStream); + } else if (mode.equals("GZIP")) { + Log.i(TAG, "GZIP compression activated"); + inputStream = new GZIPInputStream(inputStream); + } else { + Log.i(TAG, "NO compression activated"); + } + } + line = readLine(); + } else { + Log.i(TAG, "NO compression activated"); + } + + // correct answer? + if (!line.startsWith("START")) { + Log.w(TAG, line); + throw new IOException("Answer not wellformed: " + line); + } + + if (line.startsWith("START|")) { + informStartListener(line.substring(6)); + } + + // read answer lines + for (; !abort;) { + + // get next line + line = readLine(); + if (line.length() == 0) { + break; + } + + // last line? + if (line.startsWith("END")) { + break; + } + + // error? + if (line.startsWith("!ERROR")) { + Log.w(TAG, line); + String msg; + if (line.startsWith("!ERROR:")) { + msg = line.substring(7); + } else { + msg = line; + } + disconnect(); + informListener(SvdrpEvent.ERROR, new SvdrpException(msg)); + break; + } + + // delegate analysis + Result result = null; + try { + result = parseAnswer(line); + + } catch (final Exception ex) { + Log.w(TAG, ex); + disconnect(); + Log.w(TAG, "line: " + line); + informListener(SvdrpEvent.ERROR, ex); + return; + } + if (result != null) { + informListener(result); + // results.add(result); + // if (resultInfoEnabled) { + + // } + } + + } + + // disconnect + disconnect(); + + if (abort) { + informListener(SvdrpEvent.ABORTED); + } else { + informListener(SvdrpEvent.FINISHED_SUCCESS); + } + + } catch (final Exception e) { + Log.w(TAG, e); + informListener(SvdrpEvent.FINISHED_ABNORMALY, e); + } + } + + // public void setResultInfoEnabled(final boolean resultInfoEnabled) { + // this.resultInfoEnabled = resultInfoEnabled; + // } + + protected void informListener(final SvdrpEvent event, final Throwable e) { + for (final SvdrpExceptionListener listener : svdrpExceptionListeners) { + listener.svdrpEvent(event, e); + } + } + + protected void informListener(final SvdrpEvent event) { + for (final SvdrpListener listener : svdrpListeners) { + listener.svdrpEvent(event); + } + } + + protected void informListener(final Result result) { + for (final SvdrpResultListener<Result> listener : svdrpResultListeners) { + listener.svdrpEvent(result); + } + } + + protected void informStartListener(final String result) { + for (final SvdrpStartListener l : startListeners) { + l.start(result); + } + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpEvent.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpEvent.java new file mode 100644 index 0000000..a64f905 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpEvent.java @@ -0,0 +1,24 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +public enum SvdrpEvent { + CONNECTING, + CONNECTED, + CONNECT_ERROR, + LOGIN, + LOGGED_IN, + LOGIN_ERROR, + COMMAND_SENDING, + COMMAND_SENT, + //RESULT_RECEIVED, + DISCONNECTING, + DISCONNECTED, + FINISHED_ABNORMALY, + FINISHED_SUCCESS, + ABORTED, + ERROR, + CACHE_HIT, + CONNECTION_TIMEOUT + // + ; + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpException.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpException.java new file mode 100644 index 0000000..fe0824e --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpException.java @@ -0,0 +1,35 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +/** + * Class for exception caused by SVDRP errors + * @author bju + * + */ +@SuppressWarnings("serial") +public class SvdrpException extends Exception { + + SvdrpEvent event; + + public SvdrpEvent getEvent() { + return event; + } + + public void setEvent(SvdrpEvent event) { + this.event = event; + } + + public SvdrpException(SvdrpEvent event, String text) { + super(text); + } + public SvdrpException(String text) { + this(null,text); + } + + public SvdrpException(String text, Throwable cause) { + super(text, cause); + } + + public SvdrpException(Throwable cause) { + super(cause); + } +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpExceptionListener.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpExceptionListener.java new file mode 100644 index 0000000..1483e6d --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpExceptionListener.java @@ -0,0 +1,5 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +public interface SvdrpExceptionListener { + void svdrpEvent(SvdrpEvent event, Throwable t); +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpFinishedListener.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpFinishedListener.java new file mode 100644 index 0000000..7fba637 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpFinishedListener.java @@ -0,0 +1,9 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import java.util.List; + +public interface SvdrpFinishedListener<Result> { + + public void finished(List<Result> results); + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpListener.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpListener.java new file mode 100644 index 0000000..88ea35e --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpListener.java @@ -0,0 +1,9 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + + +public interface SvdrpListener{ + + + void svdrpEvent(SvdrpEvent event); + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpResultListener.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpResultListener.java new file mode 100644 index 0000000..fbea3c3 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpResultListener.java @@ -0,0 +1,6 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +public interface SvdrpResultListener<Result> { + + void svdrpEvent(Result result); +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpStartListener.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpStartListener.java new file mode 100644 index 0000000..e7c6700 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SvdrpStartListener.java @@ -0,0 +1,15 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +public interface SvdrpStartListener { + /** + * + * START is read + * + * The Start line may contain addition information separated by '|' + * + * So START|some meta info + * @param meta may be null + */ + void start(String meta); + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SwitchChannelClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SwitchChannelClient.java new file mode 100644 index 0000000..65a4d59 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/SwitchChannelClient.java @@ -0,0 +1,57 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import de.bjusystems.vdrmanager.R; + + +/** + * Class for switching a channel by SETCHANNEL <NR|CHID> + * + * @author lado + * + */ +public class SwitchChannelClient extends SvdrpClient<String> { + + private Integer nr; + + private String chid; + + public SwitchChannelClient(final Integer nr, final CertificateProblemListener certificateProblemListener){ + this(certificateProblemListener); + this.nr = nr; + } + + public SwitchChannelClient(final String chid, final CertificateProblemListener certificateProblemListener){ + this(certificateProblemListener); + this.chid = chid; + } + + /** + * Constructor + */ + public SwitchChannelClient(final CertificateProblemListener certificateProblemListener) { + super(certificateProblemListener); + } + + /** + * Starts the wakeup request + */ + @Override + public void run() { + if(nr != null){ + runCommand("SETCHANNEL " + String.valueOf(nr)); + } else { + runCommand("SETCHANNEL " + chid); + } + } + + @Override + public String parseAnswer(final String line) { + return line; + } + + @Override + public int getProgressTextId() { + return R.string.progress_switching; + } + +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/TimerClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/TimerClient.java new file mode 100644 index 0000000..1f55750 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/svdrp/TimerClient.java @@ -0,0 +1,47 @@ +package de.bjusystems.vdrmanager.utils.svdrp; + +import java.util.HashMap; +import java.util.Map; + +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Channel; +import de.bjusystems.vdrmanager.data.Timer; + +/** + * Class for retrieving informations about the running program + * @author bju + * + */ +public class TimerClient extends SvdrpClient<Timer> { + + /** channel names for timer */ + Map<String, Channel> channels; + + /** + * Constructor + * @param certificateProblemListener CertificateProblemListener + */ + public TimerClient(final CertificateProblemListener certificateProblemListener) { + super(certificateProblemListener); + this.channels = new HashMap<String, Channel>(); + } + + /** + * Starts the EPG request + */ + @Override + public synchronized void run() { + runCommand("timers conflicts"); + } + + @Override + public Timer parseAnswer(final String line) { + return new Timer(line); + } + + @Override + public int getProgressTextId() { + return R.string.progress_timers_loading; + } +} + diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/AsyncWakeupTask.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/AsyncWakeupTask.java new file mode 100644 index 0000000..5268eb4 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/AsyncWakeupTask.java @@ -0,0 +1,114 @@ +package de.bjusystems.vdrmanager.utils.wakeup; + +import android.app.ProgressDialog; +import android.content.Context; +import android.os.AsyncTask; +import android.util.Log; +import android.widget.Toast; +import de.bjusystems.vdrmanager.R; +import de.bjusystems.vdrmanager.data.Preferences; +import de.bjusystems.vdrmanager.utils.http.HttpHelper; + +public class AsyncWakeupTask extends AsyncTask<Object, WakeupProgress, Void> { + + private static final String TAG = "AsyncWakeupTask"; + /** Context */ + private final Context context; + /** Progress dialog */ + private ProgressDialog progressDialog; + + public AsyncWakeupTask(final Context context) { + this.context = context; + } + + Wakeuper getWakeuper() { + // Preferences + final Preferences prefs = Preferences.getPreferences(); + + if (Preferences.getPreferences().getWakeupMethod().equals("url")) { + return new Wakeuper() { + public void wakeup(Context context) { + // wakeup by http request + final HttpHelper httpHelper = new HttpHelper(); + httpHelper.performGet(prefs.getWakeupUrl(), + prefs.getWakeupUser(), prefs.getWakeupPassword(), + null); + // request sent + + } + }; + } else { + return new Wakeuper() { + public void wakeup(Context context) throws Exception { + WakeOnLanClient.wake(prefs.getVdrMac()); + } + }; + } + } + + @Override + protected Void doInBackground(final Object... params) { + + // open progress dialog + publishProgress(new WakeupProgress(WakeupProgressType.WAKEUP_STARTED)); + + boolean ok = false; + String msg = null; + try { + getWakeuper().wakeup(context); + ok = true; + } catch (final Exception e) { + Log.w(TAG, e); + msg = e.getMessage(); + } + + // close progress + publishProgress(new WakeupProgress(WakeupProgressType.WAKEUP_FINISHED)); + if (ok) { + publishProgress(new WakeupProgress(WakeupProgressType.WAKEUP_OK)); + } else { + publishProgress(new WakeupProgress(WakeupProgressType.WAKEUP_ERROR, + msg)); + } + + return null; + } + + @Override + protected void onProgressUpdate(final WakeupProgress... values) { + super.onProgressUpdate(values); + + WakeupProgress t = values[0]; + + switch (t.getState()) { + case WAKEUP_STARTED: + final CharSequence message = context + .getText(R.string.progress_wakeup_sending); + progressDialog = ProgressDialog.show(context, "", message); + break; + case WAKEUP_FINISHED: + progressDialog.dismiss(); + break; + case WAKEUP_OK: + showToast(R.string.progress_wakeup_sent); + break; + case WAKEUP_ERROR: + showToast(R.string.progress_wakeup_error, t.getInfo()); + break; + } + } + + private void showToast(final int textId, Object... args) { + + final CharSequence text; + if (args.length > 1) { + text = context.getString(textId); + } else { + text = context.getString(textId, args); + } + final int duration = Toast.LENGTH_SHORT; + final Toast toast = Toast.makeText(context, text, duration); + toast.show(); + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeOnLanClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeOnLanClient.java new file mode 100644 index 0000000..737195a --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeOnLanClient.java @@ -0,0 +1,140 @@ +/* + MythDroid: Android MythTV Remote + Copyright (C) 2009-2010 foobum@gmail.com + + This program 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. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package de.bjusystems.vdrmanager.utils.wakeup; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetAddress; + +import de.bjusystems.vdrmanager.data.Preferences; + +import android.text.TextUtils; +import android.util.Log; + +/** Send WakeOnLan packets */ +public class WakeOnLanClient { + + static private byte[] addr; + static private byte[] buf = new byte[17 * 6]; + + /** + * Send a wake on lan packet + * + * @param hwaddr + * String containing the MAC address of the target + */ + public static void wake(String hwaddr) throws Exception { + addr = parseAddr(hwaddr); + for (int i = 0; i < 6; i++) + buf[i] = (byte) 0xff; + for (int i = 6; i < buf.length; i += 6) + System.arraycopy(addr, 0, buf, i, 6); + // if (.debug) + Log.d("WakeOnLAN", //$NON-NLS-1$ + "Sending WOL packets to 255.255.255.255 " + //$NON-NLS-1$ + "ports 7, 9 for MAC address " + hwaddr //$NON-NLS-1$ + ); + + String broadcast = Preferences.get().getWolCustomBroadcast(); + if (TextUtils.isEmpty(broadcast) == true) { + broadcast = "255.255.255.255";//$NON-NLS-1$ + } + + InetAddress address = InetAddress.getByName(broadcast); + DatagramPacket dgram = new DatagramPacket(buf, buf.length, address, 9); + DatagramSocket sock = new DatagramSocket(); + sock.setBroadcast(true); + sock.send(dgram); + dgram.setPort(7); + sock.send(dgram); + sock.close(); + } + + /** + * Try to extract a hardware MAC address from a given IP address using the + * ARP cache (/proc/net/arp).<br> + * <br> + * We assume that the file has this structure:<br> + * <br> + * IP address HW type Flags HW address Mask Device 192.168.18.11 0x1 0x2 + * 00:04:20:06:55:1a * eth0 192.168.18.36 0x1 0x2 00:22:43:ab:2a:5b * eth0 + * + * @param ip + * @return the MAC from the ARP cache + */ + public static String getMacFromArpCache(String ip) { + if (ip == null) + return null; + BufferedReader br = null; + try { + br = new BufferedReader(new FileReader("/proc/net/arp")); + String line; + while ((line = br.readLine()) != null) { + String[] splitted = line.split(" +"); + if (splitted != null && splitted.length >= 4 + && ip.equals(splitted[0])) { + // Basic sanity check + String mac = splitted[3]; + if (mac.matches("..:..:..:..:..:..")) { + return mac; + } else { + return null; + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + br.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + /** + * + * Parse a MAC Addresse + * + * @param addr + * @return + * @throws IllegalArgumentException + */ + public static byte[] parseAddr(String addr) throws IllegalArgumentException { + byte[] bytes = new byte[6]; + String[] hex = addr.split(":"); //$NON-NLS-1$ + if (hex.length != 6) + throw new IllegalArgumentException("Invalid MAC address"); //$NON-NLS-1$ + try { + for (int i = 0; i < 6; i++) + bytes[i] = (byte) Integer.parseInt(hex[i], 16); + } catch (NumberFormatException e) { + throw new IllegalArgumentException( + "Invalid hex digit in MAC address" //$NON-NLS-1$ + ); + } + return bytes; + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeupProgress.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeupProgress.java new file mode 100644 index 0000000..f86608a --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeupProgress.java @@ -0,0 +1,26 @@ +package de.bjusystems.vdrmanager.utils.wakeup; + +public class WakeupProgress { + + public WakeupProgress(WakeupProgressType state) { + this(state, null); + } + + public WakeupProgress(WakeupProgressType state, String info) { + this.state = state; + this.info = info; + } + + private WakeupProgressType state; + + private String info; + + public WakeupProgressType getState() { + return state; + } + + public String getInfo() { + return info; + } + +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeupProgressType.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeupProgressType.java new file mode 100644 index 0000000..28dcf38 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeupProgressType.java @@ -0,0 +1,8 @@ +package de.bjusystems.vdrmanager.utils.wakeup; + +public enum WakeupProgressType { + WAKEUP_STARTED, + WAKEUP_OK, + WAKEUP_ERROR, + WAKEUP_FINISHED +} diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeupUrlClient.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeupUrlClient.java new file mode 100644 index 0000000..231554a --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/WakeupUrlClient.java @@ -0,0 +1,53 @@ +package de.bjusystems.vdrmanager.utils.wakeup; + +import de.bjusystems.vdrmanager.data.WakeupState; +import de.bjusystems.vdrmanager.utils.svdrp.CertificateProblemListener; +import de.bjusystems.vdrmanager.utils.svdrp.SvdrpClient; + +/** + * Class for retrieving informations about the running program + * @author bju + * + */ +public class WakeupUrlClient extends SvdrpClient<WakeupState> { + + private WakeupState state; + + /** + * Constructor + */ + public WakeupUrlClient(final CertificateProblemListener certificateProblemListener) { + super(certificateProblemListener); + } + + /** + * Starts the wakeup request + */ + @Override + public void run() { + runCommand("wake"); + } + + @Override + public WakeupState parseAnswer(final String line) { + + if (line.startsWith("200")) { + state = WakeupState.OK; + } else if (line.startsWith("400")) { + state = WakeupState.FAILED; + } else { + state = WakeupState.ERROR; + } + return state; + } + + @Override + public int getProgressTextId() { + return 0; + } + + public WakeupState getState() { + return state; + } + +}
\ No newline at end of file diff --git a/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/Wakeuper.java b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/Wakeuper.java new file mode 100644 index 0000000..5b149e4 --- /dev/null +++ b/vdrmanager/app/src/main/java/de/bjusystems/vdrmanager/utils/wakeup/Wakeuper.java @@ -0,0 +1,20 @@ +package de.bjusystems.vdrmanager.utils.wakeup; + +import android.content.Context; + +/** + * @author lado + * Interface to implement several wakeup methods + */ +public interface Wakeuper { + + /** + * + * + * + * @param context + * @throws Exception if the wakeup process is not ended ok. Please note, on a.e. WOL, there can + * not be no guarantee, that the host has been woken up. + */ + void wakeup(Context context) throws Exception; +} diff --git a/vdrmanager/app/src/main/java/org/fueri/reeldroid/network/DeviceManager.java b/vdrmanager/app/src/main/java/org/fueri/reeldroid/network/DeviceManager.java new file mode 100644 index 0000000..427ff35 --- /dev/null +++ b/vdrmanager/app/src/main/java/org/fueri/reeldroid/network/DeviceManager.java @@ -0,0 +1,150 @@ +package org.fueri.reeldroid.network; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; + +/*** + * + * DeviceManager singleton + * + * @author Patrick Fürlinger fueri@fueri.ch + * + * This program 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. + * + * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. + * + * + * + */ +public class DeviceManager { + + private static final int MAX_PING_TIMEOUT_MS = 40; + + public static interface ProgressListener { + void publish(String currentIP); + } + + /** + * + * @param defaultPort + * @return + */ + public static List<String> findVDRHosts(Context context, + Integer defaultPort, ProgressListener listener) { + + if (defaultPort == null) { + defaultPort = 6420; + } + + List<String> list = new ArrayList<String>(); + ConnectivityManager conMgr = (ConnectivityManager) context + .getSystemService(Context.CONNECTIVITY_SERVICE); + + WifiManager wifiManager = (WifiManager) context + .getSystemService(Context.WIFI_SERVICE); + + boolean netStatus = conMgr + .getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected() + || conMgr.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET) + .isConnected(); + + if (netStatus == false) { + return list; + } + + WifiInfo wifiInfo = wifiManager.getConnectionInfo(); + String baseIp = intToBaseIp(wifiInfo.getIpAddress()); + + for (Integer i = 1; i <= 254; i++) { + InetAddress ia; + try { + String ipHost = baseIp + i.toString(); + if (listener != null) { + listener.publish(ipHost); + } + ia = InetAddress.getByName(ipHost); + if (findHost(ia, defaultPort, MAX_PING_TIMEOUT_MS) == false) { + continue; + } + list.add(ipHost); + } catch (UnknownHostException e) { + } + + } + + return list; + } + + /** + * + * @param ip + * @param port + * @return + */ + public static boolean findHost(String ip, int port, int pingTimeout) { + InetAddress ia = null; + try { + ia = InetAddress.getByName(ip); + } catch (UnknownHostException e) { + return false; + } + return findHost(ia, port, pingTimeout); + } + + /** + * + * @param ip + * @param port + * @return + */ + public static boolean findHost(InetAddress ip, int port, int pingTimeout) { + + try { + InetAddress address = ip; + + boolean reachable = address.isReachable(pingTimeout); + + if (reachable == false) { + return false; + } + + Socket socket = new Socket(); + socket.connect(new InetSocketAddress(ip, port), 1000); + socket.close(); + return true; + } catch (Exception e) { + return false; + } + } + + /** + * return base ip as a string. + * + * @param ip_address + * @return String + */ + public static String intToBaseIp(int ip_address) { + return (ip_address & 0xFF) + "." + ((ip_address >> 8) & 0xFF) + "." + + ((ip_address >> 16) & 0xFF) + "."; + + } + +} |