diff --git a/AndroidManifest.xml b/AndroidManifest.xml index f1cb601..40f79ec 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -2,32 +2,81 @@ + android:versionName="1.6.5" > + - - - - + + + + + + + - - + + + - - + + + - + + - - - - - - - + + + + + + + + + + + + + - + + \ No newline at end of file diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar new file mode 100644 index 0000000..6080877 Binary files /dev/null and b/libs/android-support-v4.jar differ diff --git a/libs/devsmartlib.jar b/libs/devsmartlib.jar new file mode 100644 index 0000000..ae8f34f Binary files /dev/null and b/libs/devsmartlib.jar differ diff --git a/lint.xml b/lint.xml new file mode 100644 index 0000000..ee0eead --- /dev/null +++ b/lint.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/project.properties b/project.properties index 5a70945..c4f09d2 100644 --- a/project.properties +++ b/project.properties @@ -8,4 +8,4 @@ # project structure. # Project target. -target=android-7 +target=android-17 diff --git a/res/drawable/ic_error.png b/res/drawable/ic_error.png deleted file mode 100644 index cdcd800..0000000 Binary files a/res/drawable/ic_error.png and /dev/null differ diff --git a/res/drawable/ic_rec.png b/res/drawable/ic_rec.png deleted file mode 100644 index 0ed68ae..0000000 Binary files a/res/drawable/ic_rec.png and /dev/null differ diff --git a/res/drawable/ic_schedule.png b/res/drawable/ic_schedule.png deleted file mode 100644 index a03f2b0..0000000 Binary files a/res/drawable/ic_schedule.png and /dev/null differ diff --git a/res/drawable/ic_success.png b/res/drawable/ic_success.png deleted file mode 100644 index a06b49f..0000000 Binary files a/res/drawable/ic_success.png and /dev/null differ diff --git a/res/drawable/logo.png b/res/drawable/logo.png deleted file mode 100644 index f0bb399..0000000 Binary files a/res/drawable/logo.png and /dev/null differ diff --git a/res/layout/channel_list_title.xml b/res/layout/channel_list_title.xml deleted file mode 100644 index 645f163..0000000 --- a/res/layout/channel_list_title.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/channel_list_widget.xml b/res/layout/channel_list_widget.xml index 09c94af..e611cf3 100644 --- a/res/layout/channel_list_widget.xml +++ b/res/layout/channel_list_widget.xml @@ -2,21 +2,18 @@ - - + android:layout_width="fill_parent" + android:layout_marginLeft="5sp" + android:layout_marginRight="5sp"> + android:layout_height="wrap_content" + android:scaleType="centerInside" + android:layout_margin="3dip" + android:adjustViewBounds="true" + android:contentDescription="@string/icon"/> + android:src="@android:drawable/progress_horizontal" + android:contentDescription="@string/elapsed_time"/> - \ No newline at end of file diff --git a/res/layout/epgnow_list_activity.xml b/res/layout/epgnow_list_activity.xml new file mode 100644 index 0000000..632e7f8 --- /dev/null +++ b/res/layout/epgnow_list_activity.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/layout/epgnow_list_widget.xml b/res/layout/epgnow_list_widget.xml new file mode 100644 index 0000000..97283ca --- /dev/null +++ b/res/layout/epgnow_list_widget.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/epgtimeline_programme_header.xml b/res/layout/epgtimeline_programme_header.xml new file mode 100644 index 0000000..482971f --- /dev/null +++ b/res/layout/epgtimeline_programme_header.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/epgtimeline_programme_widget.xml b/res/layout/epgtimeline_programme_widget.xml new file mode 100644 index 0000000..cfb69a0 --- /dev/null +++ b/res/layout/epgtimeline_programme_widget.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/epgtimeline_widget.xml b/res/layout/epgtimeline_widget.xml new file mode 100644 index 0000000..e4093c0 --- /dev/null +++ b/res/layout/epgtimeline_widget.xml @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/res/layout/programme_layout.xml b/res/layout/programme_layout.xml index 3b530e2..1fc5bd8 100644 --- a/res/layout/programme_layout.xml +++ b/res/layout/programme_layout.xml @@ -8,13 +8,30 @@ android:layout_height="wrap_content" android:orientation="vertical" > - + android:orientation="horizontal" > + + + + + + @@ -104,9 +121,9 @@ @@ -116,6 +133,7 @@ android:layout_marginRight="5sp" android:text="@string/pr_content_type" android:textAppearance="?android:attr/textAppearanceMedium" /> + - - - - - - - - - - - \ No newline at end of file diff --git a/res/layout/programme_list_widget.xml b/res/layout/programme_list_widget.xml index 0b24cdd..331f7c8 100644 --- a/res/layout/programme_list_widget.xml +++ b/res/layout/programme_list_widget.xml @@ -24,7 +24,8 @@ android:layout_width="fill_parent" android:layout_height="18sp" android:layout_margin="3dip" - android:scaleType="fitEnd" > + android:scaleType="fitEnd" + android:contentDescription="@string/state"> @@ -37,7 +38,7 @@ diff --git a/res/layout/programme_title.xml b/res/layout/programme_title.xml deleted file mode 100644 index 98ec8f1..0000000 --- a/res/layout/programme_title.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/res/layout/recording_list_title.xml b/res/layout/recording_list_title.xml index 98ec8f1..62a18e4 100644 --- a/res/layout/recording_list_title.xml +++ b/res/layout/recording_list_title.xml @@ -9,7 +9,8 @@ android:layout_height="44sp" android:padding="3sp" android:src="@drawable/logo_72" - android:id="@+id/ct_logo"> + android:id="@+id/ct_logo" + android:contentDescription="@string/logo"> - - + android:id="@+id/rec_icon" + android:contentDescription="@string/recording"> + android:id="@+id/rec_state" + android:contentDescription="@string/state"> - \ No newline at end of file diff --git a/res/layout/recording_title.xml b/res/layout/recording_title.xml index 98ec8f1..62a18e4 100644 --- a/res/layout/recording_title.xml +++ b/res/layout/recording_title.xml @@ -9,7 +9,8 @@ android:layout_height="44sp" android:padding="3sp" android:src="@drawable/logo_72" - android:id="@+id/ct_logo"> + android:id="@+id/ct_logo" + android:contentDescription="@string/logo"> \ No newline at end of file diff --git a/res/layout/search_result_widget.xml b/res/layout/search_result_widget.xml index 8fb4947..ddcdc0d 100644 --- a/res/layout/search_result_widget.xml +++ b/res/layout/search_result_widget.xml @@ -1,84 +1,92 @@ - - + android:layout_marginLeft="5sp" + android:layout_marginRight="5sp" > + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="64sp" + android:orientation="vertical" > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/main_menu.xml b/res/menu/main_menu.xml index e9a7fd0..1d8c510 100644 --- a/res/menu/main_menu.xml +++ b/res/menu/main_menu.xml @@ -1,20 +1,33 @@ + + + + + + + + + + + + + - - - - + android:title="@string/menu_refresh" /> - - - \ No newline at end of file diff --git a/res/menu/rc_menu.xml b/res/menu/rc_menu.xml deleted file mode 100644 index 2b05352..0000000 --- a/res/menu/rc_menu.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/res/values-de/arrays.xml b/res/values-de/arrays.xml new file mode 100644 index 0000000..9b111a9 --- /dev/null +++ b/res/values-de/arrays.xml @@ -0,0 +1,30 @@ + + + + matroska + mpegts + mpegps + pass + + + + Matroska + MPEG-TS + MPEG-PS + Pass-through + + + + #55a4ff + #ff7755 + #ff8a34 + #fff899 + #55fff1 + #ff99c2 + #b8ff8c + #ff4738 + #378b2e + #63bea9 + #9ba4a2 + + \ No newline at end of file diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index dcb9f18..a45ee58 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -1,19 +1,31 @@ TVHGuide - - Hilfe + Jetzt + Stündlich + Primetime + Timeline Einstellungen Aktualisieren Tags Aufnahmen + Aufnehmen + Aufnahme abbrechen + Aufnahme entfernen + Program Liste Hostname Hostnamen des Servers eingeben - + Ansicht Port Port der Servers eingeben - + Media container + Bevorzugter Media container + Abspielen + HTTP port + Server HTTP Port + Bevorzugter externer Player + Benutze einen externen Player für Live Streams Benutzername Benutzernamen eingeben @@ -24,18 +36,33 @@ Kanal-Logos anzeigen? Zugriff verweigert - Ungültige Antwort des Servers Konnte nicht mit dem Server verbinden Verbindung zum Server verloren - Lade... - Bitte warten Sie einen Moment... - Alle Kanäle - + mehr… + Online + Bewertung + Typ + Serieninformation + Season + Episode + Part + + Abspielen +Keine Übertragung + EPG durchsuchen + heute Geplant Aufnahme Fertig Verpasst Ungültig + Status + logo + Aufnahme + Suche + vergangene Zeit + Icon + EPG Timeline \ No newline at end of file diff --git a/res/values-sv/arrays.xml b/res/values-sv/arrays.xml new file mode 100644 index 0000000..9b111a9 --- /dev/null +++ b/res/values-sv/arrays.xml @@ -0,0 +1,30 @@ + + + + matroska + mpegts + mpegps + pass + + + + Matroska + MPEG-TS + MPEG-PS + Pass-through + + + + #55a4ff + #ff7755 + #ff8a34 + #fff899 + #55fff1 + #ff99c2 + #b8ff8c + #ff4738 + #378b2e + #63bea9 + #9ba4a2 + + \ No newline at end of file diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 58b2319..8702bdd 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -2,7 +2,6 @@ TVHGuide - Hjälp Inställningar Uppdatera Taggar @@ -26,24 +25,9 @@ Användargränssnitt - Ljust tema - Använd ett ljust tema - Kanallogotyper Visa kanalers logotyper? - Upplösning - Maximal upplösning vid omkodning - - Ljud - Kodek att använda vid omkodning av ljud - - Video - Kodek att använda vid omkodning av video - - Undertexter - Kodek att använda vid omkodning av undertexter - Media container Media container att föredra @@ -52,29 +36,22 @@ HTTP port Skriv in HTTP portnummer - Koda om ström - Begär omkodning av direktsänd media - Föredra extern spelare Använd en extern spelare för direktsänd media (BSPlayer verkar OK) Åtkomst nekad - Felaktigt svar från servern Kan inte ansluta till servern Tappade anslutningen till servern - Laddar… - Var god vänta några sekunder… - Alla kanaler Hämta flera - Säsong - Avsnitt - Del Sänds Omdömme Typ Del + Season + Episode + Part Schemalagd Spelar in diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 2c4c12b..9b111a9 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1,49 +1,5 @@ - - 576 - 480 - 384 - 288 - 192 - - - 576p - 480p - 384p - 288p - 192p - - - - AAC - MPEG2AUDIO - - - Advanced Audio Codec (AAC) - MPEG-2 Audio Layer II (MP2) - - - - H264 - MPEG4VIDEO - VP8 - - - MPEG-4 AVC (H.264) - MPEG-4 Part 2 - VP8 - - - - PASS - NONE - - - Pass-through - None - - matroska mpegts @@ -57,4 +13,18 @@ MPEG-PS Pass-through + + + #55a4ff + #ff7755 + #ff8a34 + #fff899 + #55fff1 + #ff99c2 + #b8ff8c + #ff4738 + #378b2e + #63bea9 + #9ba4a2 + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index ec275ac..6889005 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,82 +1,51 @@ - - TVHGuide + - Help + TVHGuide Settings Refresh + Now + Hourly + Primetime + Timeline Tags Recordings - Record Cancel recording Remove recording - + Program list Hostname Enter Server Hostname - Port Enter Server Port - Username Enter Username - Password Enter Password - User interface - Channel icons Show channel icons? - - Light theme - Use a light theme - - Resolution - Maximum resolution for the transcoder - - Audio codec - Audio codec to use with the transcoder - - Video codec - Video codec to use with the transcoder - - Subtitle codec - Subtitle codec to use with the transcoder - Media container - Prefered media container - + Preferred media container Playback - HTTP port Enter Server HTTP Port - - Transcode stream - Request a transcoded stream during live playback - Prefer external player Use an external player for live streams (BSPlayer seems OK) - Access denied - Invalid response from server Can\'t connect to server Lost connection to server - - Loading… - Please wait a few seconds… - All channels Get more - Season - Episode - Part Airing Rating Type Part - - + Season + Episode + Part + + Movie/Drama News/Current affairs Show/Game show @@ -89,8 +58,7 @@ Leisure hobbies Misc - - + Movie/Drama Detective/Thriller Adventure/Western/War @@ -101,23 +69,20 @@ Serious/ClassicalReligion/Historical Adult Movie/Drama - - + News/Current affairs News/Weather Report Magazine Documentary Discussion/Interview/Debate - - + Show/Game show Game show/Quiz/Contest Variety Talk - - + Sports Special Event Magazine @@ -129,28 +94,25 @@ Water Sport Winter Sports Equestrian - Martial sports + Martial sports - - + Children\'s / Youth Pre-school Entertainment (6 to 14 year-olds) - Entertainment (10 to 16 year-olds) - Informational/Educational/Schools + Entertainment (10 to 16 year-olds) + Informational/Educational/Schools Cartoons/Puppets - Music/Ballet/Dance Rock/Pop - Serious music/Classical Music + Serious music/Classical Music Folk/Traditional music Jazz Musical/Opera - Ballet + Ballet - Arts/Culture Performing Arts @@ -158,24 +120,21 @@ Religion Popular Culture/Tradital Arts Literature - Film/Cinema + Film/Cinema Experimental Film/Video Broadcasting/Press New Media - Magazine + Magazine Fashion - - + Social/Political issues/Economics - Magazine/Report/Domentary + Magazine/Report/Domentary Economics/Social Advisory Remarkable People - - - - Education/Science/Factual + + Education/Science/Factual Nature/Animals/Environment Technology/Natural sciences Medicine/Physiology/Psychology @@ -184,7 +143,6 @@ Further Education Languages - Leisure hobbies Tourism/Travel @@ -195,8 +153,7 @@ Advertisement/Shopping Gardening - - + Misc Black and White Unpublished @@ -205,13 +162,19 @@ Scheduled Recording - Completed - Missed + Completed + Missed Invalid - Play No transmission - Search the EPG today + State + logo + recording + search + elapsed time + icon + epg timeline + \ No newline at end of file diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml index c67a50e..0a6cc1b 100644 --- a/res/xml/preferences.xml +++ b/res/xml/preferences.xml @@ -34,14 +34,7 @@ - - + android:title="@string/pref_ui"> tagAdapter; - private AlertDialog tagDialog; - private TextView tagTextView; - private ImageView tagImageView; - private View tagBtn; - private ProgressBar pb; - private ChannelTag currentTag; - - @Override - public void onCreate(Bundle icicle) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - Boolean theme = prefs.getBoolean("lightThemePref", false); - setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme); - - super.onCreate(icicle); - - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - - chAdapter = new ChannelListAdapter(this, new ArrayList()); - setListAdapter(chAdapter); - - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.channel_list_title); - tagTextView = (TextView) findViewById(R.id.ct_title); - tagImageView = (ImageView) findViewById(R.id.ct_logo); - - pb = (ProgressBar) findViewById(R.id.ct_loading); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.menu_tags); - - tagAdapter = new ArrayAdapter( - this, - android.R.layout.simple_dropdown_item_1line, - new ArrayList()); - - builder.setAdapter(tagAdapter, new android.content.DialogInterface.OnClickListener() { - - public void onClick(DialogInterface arg0, int pos) { - setCurrentTag(tagAdapter.getItem(pos)); - populateList(); - } - }); - - tagDialog = builder.create(); - tagBtn = findViewById(R.id.ct_btn); - tagBtn.setOnClickListener(new android.view.View.OnClickListener() { - - public void onClick(View arg0) { - tagDialog.show(); - } - }); - - registerForContextMenu(getListView()); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.main_menu, menu); - return true; - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.string.ch_play: { - startActivity(item.getIntent()); - return true; - } - case R.string.search_hint: { - startSearch(null, false, item.getIntent().getExtras(), false); - return true; - } - default: { - return false; - } - } - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - MenuItem item = menu.add(ContextMenu.NONE, R.string.ch_play, ContextMenu.NONE, R.string.ch_play); - - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Channel ch = chAdapter.getItem(info.position); - - menu.setHeaderTitle(ch.name); - Intent intent = new Intent(this, PlaybackActivity.class); - intent.putExtra("channelId", ch.id); - item.setIntent(intent); - - item = menu.add(ContextMenu.NONE, R.string.search_hint, ContextMenu.NONE, R.string.search_hint); - intent = new Intent(); - intent.putExtra("channelId", ch.id); - item.setIntent(intent); - } - - void connect(boolean force) { - if (force) { - chAdapter.clear(); - } - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - String hostname = prefs.getString("serverHostPref", "localhost"); - int port = Integer.parseInt(prefs.getString("serverPortPref", "9982")); - String username = prefs.getString("usernamePref", ""); - String password = prefs.getString("passwordPref", ""); - - Intent intent = new Intent(ChannelListActivity.this, HTSService.class); - intent.setAction(HTSService.ACTION_CONNECT); - intent.putExtra("hostname", hostname); - intent.putExtra("port", port); - intent.putExtra("username", username); - intent.putExtra("password", password); - intent.putExtra("force", force); - - startService(intent); - } - - private void setCurrentTag(ChannelTag t) { - currentTag = t; - - if (t == null) { - tagTextView.setText(R.string.pr_all_channels); - tagImageView.setImageResource(R.drawable.logo_72); - } else { - tagTextView.setText(currentTag.name); - if (currentTag.iconBitmap != null) { - tagImageView.setImageBitmap(currentTag.iconBitmap); - } else { - tagImageView.setImageResource(R.drawable.logo_72); - } - } - } - - private void populateList() { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - - chAdapter.clear(); - - for (Channel ch : app.getChannels()) { - if (currentTag == null || ch.hasTag(currentTag.id)) { - chAdapter.add(ch); - } - } - - chAdapter.sort(); - chAdapter.notifyDataSetChanged(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.mi_settings: { - Intent intent = new Intent(getBaseContext(), SettingsActivity.class); - startActivityForResult(intent, R.id.mi_settings); - return true; - } - case R.id.mi_refresh: { - connect(true); - return true; - } - case R.id.mi_recordings: { - Intent intent = new Intent(getBaseContext(), RecordingListActivity.class); - startActivity(intent); - return true; - } - case R.id.mi_search: { - onSearchRequested(); - return true; - } - case R.id.mi_tags: { - tagDialog.show(); - return true; - } - default: { - return super.onOptionsItemSelected(item); - } - } - } - - @Override - protected void onResume() { - super.onResume(); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.addListener(this); - - connect(false); - setLoading(app.isLoading()); - } - - @Override - protected void onPause() { - super.onPause(); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.removeListener(this); - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - Channel ch = (Channel) chAdapter.getItem(position); - - if (ch.epg.isEmpty()) { - return; - } - - Intent intent = new Intent(getBaseContext(), ProgrammeListActivity.class); - intent.putExtra("channelId", ch.id); - startActivity(intent); - } - - private void setLoading(boolean loading) { - tagBtn.setEnabled(!loading); - if (loading) { - pb.setVisibility(ProgressBar.VISIBLE); - tagTextView.setText(R.string.inf_load); - tagImageView.setVisibility(ImageView.INVISIBLE); - } else { - pb.setVisibility(ProgressBar.GONE); - tagImageView.setVisibility(ImageView.VISIBLE); - - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - tagAdapter.clear(); - for (ChannelTag t : app.getChannelTags()) { - tagAdapter.add(t); - } - - populateList(); - setCurrentTag(currentTag); - } - } - - public void onMessage(String action, final Object obj) { - if (action.equals(TVHGuideApplication.ACTION_LOADING)) { - - runOnUiThread(new Runnable() { - - public void run() { - boolean loading = (Boolean) obj; - setLoading(loading); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_ADD)) { - runOnUiThread(new Runnable() { - - public void run() { - chAdapter.add((Channel) obj); - chAdapter.notifyDataSetChanged(); - chAdapter.sort(); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_DELETE)) { - runOnUiThread(new Runnable() { - - public void run() { - chAdapter.remove((Channel) obj); - chAdapter.notifyDataSetChanged(); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_UPDATE)) { - runOnUiThread(new Runnable() { - - public void run() { - Channel channel = (Channel) obj; - chAdapter.updateView(getListView(), channel); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_TAG_ADD)) { - runOnUiThread(new Runnable() { - - public void run() { - ChannelTag tag = (ChannelTag) obj; - tagAdapter.add(tag); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_TAG_DELETE)) { - runOnUiThread(new Runnable() { - - public void run() { - ChannelTag tag = (ChannelTag) obj; - tagAdapter.remove(tag); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_TAG_UPDATE)) { - //NOP - } - } - - class ChannelListAdapter extends ArrayAdapter { - - ChannelListAdapter(Activity context, List list) { - super(context, R.layout.channel_list_widget, list); - } - - public void sort() { - sort(new Comparator() { - - public int compare(Channel x, Channel y) { - return x.compareTo(y); - } - }); - } - - public void updateView(ListView listView, Channel channel) { - for (int i = 0; i < listView.getChildCount(); i++) { - View view = listView.getChildAt(i); - int pos = listView.getPositionForView(view); - Channel ch = (Channel) listView.getItemAtPosition(pos); - - if (view.getTag() == null || ch == null) { - continue; - } - - if (channel.id != ch.id) { - continue; - } - - ChannelListViewWrapper wrapper = (ChannelListViewWrapper) view.getTag(); - wrapper.repaint(channel); - } - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View row = convertView; - ChannelListViewWrapper wrapper; - - Channel ch = getItem(position); - Activity activity = (Activity) getContext(); - - if (row == null) { - LayoutInflater inflater = activity.getLayoutInflater(); - row = inflater.inflate(R.layout.channel_list_widget, null, false); - row.requestLayout(); - wrapper = new ChannelListViewWrapper(row); - row.setTag(wrapper); - - } else { - wrapper = (ChannelListViewWrapper) row.getTag(); - } - - wrapper.repaint(ch); - return row; - } - } + private ChannelListAdapter chAdapter; + ArrayAdapter tagAdapter; + private AlertDialog tagDialog; + private ChannelTag currentTag; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + chAdapter = new ChannelListAdapter(this, new ArrayList()); + setListAdapter(chAdapter); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.menu_tags); + + tagAdapter = new ArrayAdapter(this, + android.R.layout.simple_dropdown_item_1line, + new ArrayList()); + + builder.setAdapter(tagAdapter, + new android.content.DialogInterface.OnClickListener() { + + public void onClick(DialogInterface arg0, int pos) { + setCurrentTag(tagAdapter.getItem(pos)); + populateList(); + } + }); + + tagDialog = builder.create(); + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + currentTag = app.getCurrentTag(); + + registerForContextMenu(getListView()); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main_menu, menu); + + setCurrentTag(currentTag); + + return true; + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.string.ch_play: { + startActivity(item.getIntent()); + return true; + } + case R.string.search_hint: { + startSearch(null, false, item.getIntent().getExtras(), false); + return true; + } + default: { + return false; + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuItem item = menu.add(ContextMenu.NONE, R.string.ch_play, + ContextMenu.NONE, R.string.ch_play); + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + Channel ch = chAdapter.getItem(info.position); + + menu.setHeaderTitle(ch.name); + Intent intent = new Intent(this, PlaybackActivity.class); + intent.putExtra("channelId", ch.id); + item.setIntent(intent); + + item = menu.add(ContextMenu.NONE, R.string.search_hint, + ContextMenu.NONE, R.string.search_hint); + intent = new Intent(); + intent.putExtra("channelId", ch.id); + item.setIntent(intent); + } + + void connect(boolean force) { + if (force) { + chAdapter.clear(); + } + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + String hostname = prefs.getString("serverHostPref", "localhost"); + int port = Integer.parseInt(prefs.getString("serverPortPref", "9982")); + String username = prefs.getString("usernamePref", ""); + String password = prefs.getString("passwordPref", ""); + + Intent intent = new Intent(ChannelListActivity.this, HTSService.class); + intent.setAction(HTSService.ACTION_CONNECT); + intent.putExtra("hostname", hostname); + intent.putExtra("port", port); + intent.putExtra("username", username); + intent.putExtra("password", password); + intent.putExtra("force", force); + + startService(intent); + } + + private void setCurrentTag(ChannelTag t) { + currentTag = t; + + if (getActionBar() != null) { + + if (t == null) { + getActionBar().setTitle(R.string.pr_all_channels); + getActionBar().setIcon(R.drawable.logo_72); + } else { + getActionBar().setTitle(currentTag.name); + getActionBar().setIcon(R.drawable.logo_72); + + if (currentTag.iconBitmap != null) { + getActionBar().setIcon( + new BitmapDrawable(getResources(), + currentTag.iconBitmap)); + } else { + getActionBar().setIcon(R.drawable.logo_72); + } + } + } + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.setCurrentTag(t); + } + + private void populateList() { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + + chAdapter.clear(); + + for (Channel ch : app.getChannels()) { + if (currentTag == null || ch.hasTag(currentTag.id)) { + chAdapter.add(ch); + } + } + + chAdapter.sort(); + chAdapter.notifyDataSetChanged(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.mi_settings: { + Intent intent = new Intent(getBaseContext(), SettingsActivity.class); + startActivityForResult(intent, R.id.mi_settings); + return true; + } + case R.id.mi_refresh: { + connect(true); + return true; + } + case R.id.mi_recordings: { + Intent intent = new Intent(getBaseContext(), + RecordingListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_epg_prime: { + Intent intent = new Intent(getBaseContext(), + EPGPrimeTimeListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_epg_list: { + Intent intent = new Intent(getBaseContext(), + EPGHourlyTimeListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_channels: { + return true; + } + case R.id.mi_epg_timeline: { + Intent intent = new Intent(getBaseContext(), + EPGTimelineActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_search: { + onSearchRequested(); + return true; + } + case R.id.mi_tags: { + tagDialog.show(); + return true; + } + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + protected void onResume() { + super.onResume(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.addListener(this); + + connect(false); + setLoading(app.isLoading()); + } + + @Override + protected void onPause() { + super.onPause(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.removeListener(this); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Channel ch = (Channel) chAdapter.getItem(position); + + if (ch.epg.isEmpty()) { + return; + } + + Intent intent = new Intent(getBaseContext(), + ProgrammeListActivity.class); + intent.putExtra("channelId", ch.id); + startActivity(intent); + } + + private void setLoading(boolean loading) { + if (loading) { + // pb.setVisibility(ProgressBar.VISIBLE); + // getActionBar().setTitle(R.string.inf_load); + } else { + // pb.setVisibility(ProgressBar.GONE); + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + tagAdapter.clear(); + for (ChannelTag t : app.getChannelTags()) { + tagAdapter.add(t); + } + + populateList(); + setCurrentTag(currentTag); + } + } + + public void onMessage(String action, final Object obj) { + if (action.equals(TVHGuideApplication.ACTION_LOADING)) { + + runOnUiThread(new Runnable() { + + public void run() { + boolean loading = (Boolean) obj; + setLoading(loading); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_ADD)) { + runOnUiThread(new Runnable() { + + public void run() { + chAdapter.add((Channel) obj); + chAdapter.notifyDataSetChanged(); + chAdapter.sort(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_DELETE)) { + runOnUiThread(new Runnable() { + + public void run() { + chAdapter.remove((Channel) obj); + chAdapter.notifyDataSetChanged(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Channel channel = (Channel) obj; + chAdapter.updateView(getListView(), channel); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_TAG_ADD)) { + runOnUiThread(new Runnable() { + + public void run() { + ChannelTag tag = (ChannelTag) obj; + tagAdapter.add(tag); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_TAG_DELETE)) { + runOnUiThread(new Runnable() { + + public void run() { + ChannelTag tag = (ChannelTag) obj; + tagAdapter.remove(tag); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_TAG_UPDATE)) { + // NOP + } + } + + class ChannelListAdapter extends ArrayAdapter { + + ChannelListAdapter(Activity context, List list) { + super(context, R.layout.channel_list_widget, list); + } + + public void sort() { + sort(new Comparator() { + + public int compare(Channel x, Channel y) { + return x.compareTo(y); + } + }); + } + + public void updateView(ListView listView, Channel channel) { + for (int i = 0; i < listView.getChildCount(); i++) { + View view = listView.getChildAt(i); + int pos = listView.getPositionForView(view); + Channel ch = (Channel) listView.getItemAtPosition(pos); + + if (view.getTag() == null || ch == null) { + continue; + } + + if (channel.id != ch.id) { + continue; + } + + ChannelListViewWrapper wrapper = (ChannelListViewWrapper) view + .getTag(); + wrapper.repaint(channel); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + ChannelListViewWrapper wrapper; + + Channel ch = getItem(position); + Activity activity = (Activity) getContext(); + + if (row == null) { + LayoutInflater inflater = activity.getLayoutInflater(); + row = inflater.inflate(R.layout.channel_list_widget, null, + false); + row.requestLayout(); + wrapper = new ChannelListViewWrapper(row); + row.setTag(wrapper); + + } else { + wrapper = (ChannelListViewWrapper) row.getTag(); + } + + wrapper.repaint(ch); + return row; + } + } } diff --git a/src/org/tvheadend/tvhguide/ChannelListViewWrapper.java b/src/org/tvheadend/tvhguide/ChannelListViewWrapper.java index 37c68d6..2642674 100644 --- a/src/org/tvheadend/tvhguide/ChannelListViewWrapper.java +++ b/src/org/tvheadend/tvhguide/ChannelListViewWrapper.java @@ -18,7 +18,14 @@ */ package org.tvheadend.tvhguide; +import java.util.Date; +import java.util.Iterator; + +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.Programme; + import android.content.SharedPreferences; +import android.content.res.Resources; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ClipDrawable; import android.preference.PreferenceManager; @@ -27,97 +34,100 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; -import java.util.Date; -import java.util.Iterator; -import org.tvheadend.tvhguide.R; -import org.tvheadend.tvhguide.model.Channel; -import org.tvheadend.tvhguide.model.Programme; /** - * + * * @author john-tornblom */ public class ChannelListViewWrapper { - private TextView name; - private TextView nowTitle; - private TextView nowTime; - private TextView nextTitle; - private TextView nextTime; - private ImageView icon; - private ImageView nowProgressImage; - private ClipDrawable nowProgress; - - public ChannelListViewWrapper(View base) { - name = (TextView) base.findViewById(R.id.ch_name); - nowTitle = (TextView) base.findViewById(R.id.ch_now_title); - - nowProgressImage = (ImageView) base.findViewById(R.id.ch_elapsedtime); - nowProgress = new ClipDrawable(nowProgressImage.getDrawable(), Gravity.LEFT, ClipDrawable.HORIZONTAL); - nowProgressImage.setBackgroundDrawable(nowProgress); - - nowTime = (TextView) base.findViewById(R.id.ch_now_time); - nextTitle = (TextView) base.findViewById(R.id.ch_next_title); - nextTime = (TextView) base.findViewById(R.id.ch_next_time); - icon = (ImageView) base.findViewById(R.id.ch_icon); - } - - public void repaint(Channel channel) { - nowTime.setText(""); - nowTitle.setText(""); - nextTime.setText(""); - nextTitle.setText(""); - nowProgress.setLevel(0); - - name.setText(channel.name); - name.invalidate(); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(icon.getContext()); - Boolean showIcons = prefs.getBoolean("showIconPref", false); - icon.setVisibility(showIcons ? ImageView.VISIBLE : ImageView.GONE); - icon.setBackgroundDrawable(new BitmapDrawable(channel.iconBitmap)); - - if (channel.isRecording()) { - icon.setImageResource(R.drawable.ic_rec_small); - } else { - icon.setImageDrawable(null); - } - icon.invalidate(); - - Iterator it = channel.epg.iterator(); - if (!channel.isTransmitting && it.hasNext()) { - nowTitle.setText(R.string.ch_no_transmission); - } else if (it.hasNext()) { - Programme p = it.next(); - nowTime.setText( - DateFormat.getTimeFormat(nowTime.getContext()).format(p.start) - + " - " - + DateFormat.getTimeFormat(nowTime.getContext()).format(p.stop)); - - double duration = (p.stop.getTime() - p.start.getTime()); - double elapsed = new Date().getTime() - p.start.getTime(); - double percent = elapsed / duration; - - nowProgressImage.setVisibility(ImageView.VISIBLE); - nowProgress.setLevel((int) Math.floor(percent * 10000)); - nowTitle.setText(p.title); - } else { - nowProgressImage.setVisibility(ImageView.GONE); - } - nowProgressImage.invalidate(); - nowTime.invalidate(); - nowTitle.invalidate(); - - if (it.hasNext()) { - Programme p = it.next(); - nextTime.setText( - DateFormat.getTimeFormat(nextTime.getContext()).format(p.start) - + " - " - + DateFormat.getTimeFormat(nextTime.getContext()).format(p.stop)); - - nextTitle.setText(p.title); - } - nextTime.invalidate(); - nextTitle.invalidate(); - } + private TextView name; + private TextView nowTitle; + private TextView nowTime; + private TextView nextTitle; + private TextView nextTime; + private ImageView icon; + private ImageView nowProgressImage; + private ClipDrawable nowProgress; + private Resources resources; + + public ChannelListViewWrapper(View base) { + name = (TextView) base.findViewById(R.id.ch_name); + nowTitle = (TextView) base.findViewById(R.id.ch_now_title); + + nowProgressImage = (ImageView) base.findViewById(R.id.ch_elapsedtime); + nowProgress = new ClipDrawable(nowProgressImage.getDrawable(), + Gravity.LEFT, ClipDrawable.HORIZONTAL); + nowProgressImage.setImageDrawable(nowProgress); + + nowTime = (TextView) base.findViewById(R.id.ch_now_time); + nextTitle = (TextView) base.findViewById(R.id.ch_next_title); + nextTime = (TextView) base.findViewById(R.id.ch_next_time); + icon = (ImageView) base.findViewById(R.id.ch_icon); + resources = base.getResources(); + } + + public void repaint(Channel channel) { + nowTime.setText(""); + nowTitle.setText(""); + nextTime.setText(""); + nextTitle.setText(""); + nowProgress.setLevel(0); + + name.setText(channel.name); + name.invalidate(); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(icon.getContext()); + Boolean showIcons = prefs.getBoolean("showIconPref", false); + icon.setVisibility(showIcons ? ImageView.VISIBLE : ImageView.GONE); + BitmapDrawable bitmapDrawable = new BitmapDrawable(resources, + channel.iconBitmap); + icon.setBackgroundDrawable(bitmapDrawable); + + if (channel.isRecording()) { + icon.setImageResource(R.drawable.ic_rec_small); + } else { + icon.setImageDrawable(null); + } + icon.invalidate(); + + Iterator it = channel.epg.iterator(); + if (!channel.isTransmitting && it.hasNext()) { + nowTitle.setText(R.string.ch_no_transmission); + } else if (it.hasNext()) { + Programme p = it.next(); + nowTime.setText(DateFormat.getTimeFormat(nowTime.getContext()) + .format(p.start) + + " - " + + DateFormat.getTimeFormat(nowTime.getContext()).format( + p.stop)); + + double duration = (p.stop.getTime() - p.start.getTime()); + double elapsed = new Date().getTime() - p.start.getTime(); + double percent = elapsed / duration; + + nowProgressImage.setVisibility(ImageView.VISIBLE); + nowProgress.setLevel((int) Math.floor(percent * 10000)); + nowTitle.setText(p.title); + } else { + nowProgressImage.setVisibility(ImageView.GONE); + } + nowProgressImage.invalidate(); + nowTime.invalidate(); + nowTitle.invalidate(); + + if (it.hasNext()) { + Programme p = it.next(); + nextTime.setText(DateFormat.getTimeFormat(nextTime.getContext()) + .format(p.start) + + " - " + + DateFormat.getTimeFormat(nextTime.getContext()).format( + p.stop)); + + nextTitle.setText(p.title); + } + nextTime.invalidate(); + nextTitle.invalidate(); + } } diff --git a/src/org/tvheadend/tvhguide/EPGHourlyTimeListActivity.java b/src/org/tvheadend/tvhguide/EPGHourlyTimeListActivity.java new file mode 100644 index 0000000..543fda7 --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGHourlyTimeListActivity.java @@ -0,0 +1,35 @@ +package org.tvheadend.tvhguide; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; + +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +public class EPGHourlyTimeListActivity extends EPGTimeListActivity { + + private static final int DEFAULT_HOURS = 24; + + @Override + protected List createTimeSlots() { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + + List timeSlots = new ArrayList(); + + Calendar cal = Calendar.getInstance(); + cal.clear(Calendar.MINUTE); + cal.clear(Calendar.SECOND); + cal.clear(Calendar.MILLISECOND); + + int maxHours = prefs.getInt("epg.hourly.timeslots", DEFAULT_HOURS); + for (int i = 0; i < maxHours; i++) { + timeSlots.add(cal.getTime()); + cal.add(Calendar.HOUR_OF_DAY, 1); + } + return timeSlots; + } + +} diff --git a/src/org/tvheadend/tvhguide/EPGPrimeTimeListActivity.java b/src/org/tvheadend/tvhguide/EPGPrimeTimeListActivity.java new file mode 100644 index 0000000..bf18253 --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGPrimeTimeListActivity.java @@ -0,0 +1,100 @@ +package org.tvheadend.tvhguide; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.text.format.DateFormat; + +public class EPGPrimeTimeListActivity extends EPGTimeListActivity { + + private static final int SLOTS = 24; + + @SuppressWarnings("deprecation") + @Override + protected List createTimeSlots() { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + + java.text.DateFormat format = DateFormat.getTimeFormat(this); + + // TODO: define primetime slots via settings + Set defaultPrimeTimeSlots = new TreeSet(); + defaultPrimeTimeSlots.add("12:00"); + defaultPrimeTimeSlots.add("18:30"); + defaultPrimeTimeSlots.add("20:15"); + defaultPrimeTimeSlots.add("22:00"); + defaultPrimeTimeSlots.add("24:00"); + + List primeTimeSlots = new ArrayList(prefs.getStringSet( + "epg.prime.timeslots", defaultPrimeTimeSlots)); + + List timeSlots = new ArrayList(); + + // + Calendar cal = Calendar.getInstance(); + cal.clear(Calendar.SECOND); + cal.clear(Calendar.MILLISECOND); + cal.add(Calendar.HOUR_OF_DAY, -2); + + List primeTimeSlotDates = new ArrayList(); + try { + for (String dateStr : primeTimeSlots) { + primeTimeSlotDates.add(format.parse(dateStr)); + } + } catch (ParseException e) { + e.printStackTrace(); + return timeSlots; + } + + int primeSlotIndex = 0; + + // first find timeslot occurance after actual time - 2hours + long lastDiff = -1; + for (int i = primeTimeSlots.size() - 1; i >= 0; --i) { + Date time = primeTimeSlotDates.get(i); + int hour = time.getHours(); + int minute = time.getMinutes(); + + Calendar cal2 = Calendar.getInstance(); + cal2.set(Calendar.HOUR_OF_DAY, hour); + cal2.set(Calendar.MINUTE, minute); + cal2.clear(Calendar.SECOND); + cal2.clear(Calendar.MILLISECOND); + + if (cal2.after(cal)) { + long diff = cal2.getTimeInMillis() - cal.getTimeInMillis(); + if (lastDiff == -1 || diff < lastDiff) { + primeSlotIndex = i; + } + lastDiff = diff; + } + } + + for (int i = 0; i < SLOTS; i++) { + Date time = primeTimeSlotDates.get(primeSlotIndex++); + primeSlotIndex %= primeTimeSlotDates.size(); + int hour = time.getHours(); + int minute = time.getMinutes(); + + // find next occurange of timeslot after actual time + int currHour = cal.get(Calendar.HOUR_OF_DAY); + while (currHour != hour) { + cal.add(Calendar.HOUR_OF_DAY, 1); + currHour = cal.get(Calendar.HOUR_OF_DAY); + } + cal.set(Calendar.MINUTE, minute); + + timeSlots.add(cal.getTime()); + cal.add(Calendar.HOUR_OF_DAY, 1); + } + return timeSlots; + } + +} diff --git a/src/org/tvheadend/tvhguide/EPGTimeListActivity.java b/src/org/tvheadend/tvhguide/EPGTimeListActivity.java new file mode 100644 index 0000000..7ecb199 --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGTimeListActivity.java @@ -0,0 +1,713 @@ +package org.tvheadend.tvhguide; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.Iterator; +import java.util.List; + +import org.tvheadend.tvhguide.htsp.HTSListener; +import org.tvheadend.tvhguide.htsp.HTSService; +import org.tvheadend.tvhguide.intent.SearchEPGIntent; +import org.tvheadend.tvhguide.intent.SearchIMDbIntent; +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.ChannelTag; +import org.tvheadend.tvhguide.model.Programme; +import org.tvheadend.tvhguide.model.Recording; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.app.ListFragment; +import android.support.v4.view.ViewPager; +import android.text.format.DateFormat; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AbsListView; +import android.widget.AbsListView.OnScrollListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +/** + * + * @author mike.toggweiler time based epg list. Show next starting programm by + * channel + * + */ +public abstract class EPGTimeListActivity extends FragmentActivity { + + private static final int DEFAULT_EPG_LIST_MAX_START_TIME = 30; + + /** + * The serialization (saved instance state) Bundle key representing the + * current tab position. + */ + private static final String STATE_SELECTED_NAVIGATION_ITEM = "selected_navigation_item"; + + /** + * The {@link android.support.v4.view.PagerAdapter} that will provide + * fragments for each of the sections. We use a + * {@link android.support.v4.app.FragmentPagerAdapter} derivative, which + * will keep every loaded fragment in memory. If this becomes too memory + * intensive, it may be best to switch to a + * {@link android.support.v4.app.FragmentStatePagerAdapter}. + */ + SectionsPagerAdapter mSectionsPagerAdapter; + + /** + * The {@link ViewPager} that will host the section contents. + */ + ViewPager mViewPager; + + private AlertDialog tagDialog; + + private int firstVibleItem = 0; + private int top = 0; + private boolean lock; + + private List m_fragmentListeners = new ArrayList(); + + private ArrayAdapter tagAdapter; + + private int m_maxStartTimeAfterTimeSlotInMinutes; + + protected abstract List createTimeSlots(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + super.onCreate(savedInstanceState); + setContentView(R.layout.epgnow_list_activity); + + // create timelost based on actual time + List timeSlots = createTimeSlots(); + m_maxStartTimeAfterTimeSlotInMinutes = prefs + .getInt("epg.timeslots.max_start_time", + DEFAULT_EPG_LIST_MAX_START_TIME); + + // Create the adapter that will return a fragment for each of the three + // primary sections of the app. + mSectionsPagerAdapter = new SectionsPagerAdapter( + getSupportFragmentManager(), + timeSlots.toArray(new Date[timeSlots.size()])); + + // Set up the ViewPager with the sections adapter. + mViewPager = (ViewPager) findViewById(R.id.pager); + mViewPager.setAdapter(mSectionsPagerAdapter); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.menu_tags); + + tagAdapter = new ArrayAdapter(this, + android.R.layout.simple_dropdown_item_1line, + new ArrayList()); + + builder.setAdapter(tagAdapter, + new android.content.DialogInterface.OnClickListener() { + + public void onClick(DialogInterface arg0, int pos) { + ChannelTag tag = tagAdapter.getItem(pos); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.setCurrentTag(tag); + + setCurrentTag(tag); + notifyEPGChannelListChanged(); + } + }); + + tagDialog = builder.create(); + } + + @Override + public void onRestoreInstanceState(Bundle savedInstanceState) { + // Restore the previously serialized current tab position. + if (savedInstanceState.containsKey(STATE_SELECTED_NAVIGATION_ITEM)) { + getActionBar().setSelectedNavigationItem( + savedInstanceState.getInt(STATE_SELECTED_NAVIGATION_ITEM)); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + setCurrentTag(app.getCurrentTag()); + + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.mi_settings: { + Intent intent = new Intent(getBaseContext(), SettingsActivity.class); + startActivityForResult(intent, R.id.mi_settings); + return true; + } + case R.id.mi_refresh: { + // connect(true); + // return true; + } + case R.id.mi_recordings: { + Intent intent = new Intent(getBaseContext(), + RecordingListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_epg_prime: { + if (getClass().isAssignableFrom(EPGPrimeTimeListActivity.class)) { + return true; + } + Intent intent = new Intent(getBaseContext(), + EPGPrimeTimeListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_epg_list: { + if (getClass().isAssignableFrom(EPGHourlyTimeListActivity.class)) { + return true; + } + Intent intent = new Intent(getBaseContext(), + EPGHourlyTimeListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_channels: { + Intent intent = new Intent(getBaseContext(), + ChannelListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_epg_timeline: { + Intent intent = new Intent(getBaseContext(), + EPGTimelineActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_search: { + onSearchRequested(); + return true; + } + case R.id.mi_tags: { + tagDialog.show(); + return true; + } + default: { + return super.onOptionsItemSelected(item); + } + } + } + + /** + * A {@link FragmentPagerAdapter} that returns a fragment corresponding to + * one of the sections/tabs/pages. + */ + public class SectionsPagerAdapter extends FragmentPagerAdapter { + + private final Date[] timeslots; + + public SectionsPagerAdapter(FragmentManager fm, Date[] timeslots) { + super(fm); + this.timeslots = timeslots; + } + + @Override + public Fragment getItem(int position) { + // When the given tab is selected, show the tab contents in the + // container view. + EPGListFragment fragment = new EPGListFragment(); + Bundle args = new Bundle(); + args.putSerializable(EPGListFragment.ARG_TIME_SLOT, + timeslots[position]); + fragment.setArguments(args); + + return fragment; + } + + @Override + public int getCount() { + return timeslots.length; + } + + @Override + public CharSequence getPageTitle(int position) { + return DateFormat.format("E k:mm", timeslots[position]); + } + + } + + private void registerEPGFragmentListener(EPGFragmentListener listener) { + m_fragmentListeners.add(listener); + } + + private void unregisterEPGFragmentListener(EPGFragmentListener listener) { + m_fragmentListeners.remove(listener); + } + + private void notifyEPGScrollListener(AbsListView view, int position, int top) { + if (lock) { + return; + } + try { + lock = true; + this.firstVibleItem = position; + this.top = top; + for (EPGFragmentListener listener : m_fragmentListeners) { + listener.scrollTo(view, position, top); + } + } finally { + lock = false; + } + } + + private void notifyEPGChannelListChanged() { + for (EPGFragmentListener listener : m_fragmentListeners) { + listener.populateChannelList(); + } + } + + private void setLoading(boolean loading) { + if (loading) { + // + } else { + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + tagAdapter.clear(); + for (ChannelTag t : app.getChannelTags()) { + tagAdapter.add(t); + } + setCurrentTag(app.getCurrentTag()); + } + } + + private void setCurrentTag(ChannelTag t) { + if (getActionBar() != null) { + + if (t == null) { + getActionBar().setTitle(R.string.pr_all_channels); + getActionBar().setIcon(R.drawable.logo_72); + } else { + getActionBar().setTitle(t.name); + getActionBar().setIcon(R.drawable.logo_72); + + if (t.iconBitmap != null) { + getActionBar().setIcon( + new BitmapDrawable(getResources(), t.iconBitmap)); + } else { + getActionBar().setIcon(R.drawable.logo_72); + } + } + } + } + + private int getTop() { + return top; + } + + private int getFirstVisibleItem() { + return firstVibleItem; + } + + /** + * A dummy fragment representing a section of the app, but that simply + * displays dummy text. + */ + public static class EPGListFragment extends ListFragment implements + HTSListener, EPGFragmentListener { + /** + * The fragment argument representing the section number for this + * fragment. + */ + public static final String ARG_TIME_SLOT = "time_slot"; + + private EPGTimeListAdapter prAdapter; + + private Date m_timeslot; + + private boolean mCreated; + + public EPGListFragment() { + } + + @Override + public void setArguments(Bundle args) { + super.setArguments(args); + m_timeslot = (Date) args.getSerializable(ARG_TIME_SLOT); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // String timeSlot = getArguments().getString(ARG_TIME_SLOT); + prAdapter = new EPGTimeListAdapter(getActivity(), + new ArrayList(), m_timeslot); + prAdapter.sort(); + setListAdapter(prAdapter); + + mCreated = true; + } + + @Override + public void onResume() { + super.onResume(); + TVHGuideApplication app = (TVHGuideApplication) getActivity() + .getApplication(); + app.addListener(this); + + // scroll to aquired position + int firstVisibleItem = ((EPGTimeListActivity) getActivity()) + .getFirstVisibleItem(); + int top = ((EPGTimeListActivity) getActivity()).getTop(); + getListView().setSelectionFromTop(firstVisibleItem, top); + + ((EPGTimeListActivity) getActivity()) + .registerEPGFragmentListener(this); + + setLoading(app.isLoading()); + } + + @Override + public void onPause() { + TVHGuideApplication app = (TVHGuideApplication) getActivity() + .getApplication(); + app.removeListener(this); + + ((EPGTimeListActivity) getActivity()) + .unregisterEPGFragmentListener(this); + + super.onPause(); + } + + private void setLoading(boolean loading) { + ((EPGTimeListActivity) getActivity()).setLoading(loading); + if (loading) { + // + } else { + populateChannelList(); + } + } + + @Override + public void populateChannelList() { + TVHGuideApplication app = (TVHGuideApplication) getActivity() + .getApplication(); + ChannelTag currentTag = app.getCurrentTag(); + prAdapter.clear(); + + for (Channel ch : app.getChannels()) { + if (currentTag == null || ch.hasTag(currentTag.id)) { + prAdapter.add(ch); + } + } + + prAdapter.sort(); + prAdapter.notifyDataSetChanged(); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + Programme p = (Programme) prAdapter.getProgrammeAt(position); + + Intent intent = new Intent(getActivity(), ProgrammeActivity.class); + intent.putExtra("eventId", p.id); + intent.putExtra("channelId", p.channel.id); + startActivity(intent); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + registerForContextMenu(getListView()); + + // scroll to acquired position + int firstVisibleItem = ((EPGTimeListActivity) getActivity()) + .getFirstVisibleItem(); + int top = ((EPGTimeListActivity) getActivity()).getTop(); + getListView().setSelectionFromTop(firstVisibleItem, top); + + getListView().setOnScrollListener(new OnScrollListener() { + @Override + public void onScrollStateChanged(AbsListView view, + int scrollState) { + + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, + int visibleItemCount, int totalItemCount) { + if (visibleItemCount == 0) { + return; + } + View v = view.getChildAt(0); + int top = (v == null) ? 0 : v.getTop(); + + int oldFirstVisibleItem = ((EPGTimeListActivity) getActivity()) + .getFirstVisibleItem(); + int oldTop = ((EPGTimeListActivity) getActivity()).getTop(); + if (firstVisibleItem != oldFirstVisibleItem + || top != oldTop) { + + ((EPGTimeListActivity) getActivity()) + .notifyEPGScrollListener(view, + firstVisibleItem, top); + } + } + }); + } + + @Override + public void scrollTo(AbsListView view, int position, int top) { + if (getListView() != view) { + getListView().setSelectionFromTop(position, top); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.string.menu_record: + case R.string.menu_record_cancel: + case R.string.menu_record_remove: { + getActivity().startService(item.getIntent()); + return true; + } + default: { + return super.onContextItemSelected(item); + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + if (info == null) { + return; + } + Channel channel = prAdapter.getItem(info.position); + Programme p = prAdapter.getProgrammeAt(info.position); + + if (p == null) { + return; + } + + menu.setHeaderTitle(p.title); + + Intent intent = new Intent(getActivity(), HTSService.class); + + MenuItem item = null; + + if (p != null) { + if (p.recording == null) { + intent.setAction(HTSService.ACTION_DVR_ADD); + intent.putExtra("eventId", p.id); + intent.putExtra("channelId", p.channel.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record, + ContextMenu.NONE, R.string.menu_record); + } else if (p.isRecording() || p.isScheduled()) { + intent.setAction(HTSService.ACTION_DVR_CANCEL); + intent.putExtra("id", p.recording.id); + item = menu.add(ContextMenu.NONE, + R.string.menu_record_cancel, ContextMenu.NONE, + R.string.menu_record_cancel); + } else { + intent.setAction(HTSService.ACTION_DVR_DELETE); + intent.putExtra("id", p.recording.id); + item = menu.add(ContextMenu.NONE, + R.string.menu_record_remove, ContextMenu.NONE, + R.string.menu_record_remove); + } + item.setIntent(intent); + + item = menu.add(ContextMenu.NONE, R.string.search_hint, + ContextMenu.NONE, R.string.search_hint); + item.setIntent(new SearchEPGIntent(getActivity(), p.title)); + + item = menu.add(ContextMenu.NONE, ContextMenu.NONE, + ContextMenu.NONE, "IMDb"); + item.setIntent(new SearchIMDbIntent(getActivity(), p.title)); + } + if (channel != null) { + intent = new Intent(getActivity().getBaseContext(), + ProgrammeListActivity.class); + intent.putExtra("channelId", channel.id); + item = menu.add(ContextMenu.NONE, + R.string.menu_show_program_list, ContextMenu.NONE, + R.string.menu_show_program_list); + item.setIntent(intent); + } + } + + @Override + public void onMessage(String action, final Object obj) { + if (action.equals(TVHGuideApplication.ACTION_LOADING)) { + + getActivity().runOnUiThread(new Runnable() { + + public void run() { + boolean loading = (Boolean) obj; + setLoading(loading); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_ADD)) { + getActivity().runOnUiThread(new Runnable() { + + public void run() { + prAdapter.add((Channel) obj); + prAdapter.notifyDataSetChanged(); + prAdapter.sort(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_DELETE)) { + getActivity().runOnUiThread(new Runnable() { + + public void run() { + prAdapter.remove((Channel) obj); + prAdapter.notifyDataSetChanged(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_UPDATE)) { + getActivity().runOnUiThread(new Runnable() { + + public void run() { + if (mCreated) { + try { + Channel channel = (Channel) obj; + prAdapter.updateView(getListView(), channel); + } catch (Exception e) { + } + } + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_ADD)) { + getActivity().runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + try { + if (mCreated) { + prAdapter.updateView(getListView(), p.channel); + } + } catch (Exception e) { + } + } + }); + } else if (action + .equals(TVHGuideApplication.ACTION_PROGRAMME_DELETE)) { + getActivity().runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + if (mCreated) { + try { + prAdapter.updateView(getListView(), p.channel); + } catch (Exception e) { + } + } + } + }); + } else if (action + .equals(TVHGuideApplication.ACTION_PROGRAMME_UPDATE)) { + getActivity().runOnUiThread(new Runnable() { + + public void run() { + if (mCreated) { + try { + Programme p = (Programme) obj; + prAdapter.updateView(getListView(), p.channel); + } catch (Exception e) { + } + } + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_DVR_UPDATE)) { + getActivity().runOnUiThread(new Runnable() { + + public void run() { + Recording rec = (Recording) obj; + for (Channel c : prAdapter.list) { + for (Programme p : c.epg) { + if (rec == p.recording) { + if (mCreated) { + try { + prAdapter.updateView(getListView(), + c); + return; + } catch (Exception e) { + } + } + } + } + } + } + }); + } + } + } + + static interface EPGFragmentListener { + public void scrollTo(AbsListView view, int position, int top); + + public void populateChannelList(); + } + + /** + * get next program based on channel and timeslot + * + * @return + */ + public Programme getProgrammeStartingAfter(Channel channel, Date timeSlot) { + Iterator it = new ArrayList(channel.epg) + .iterator(); + + Calendar cal = Calendar.getInstance(); + cal.setTime(timeSlot); + cal.add(Calendar.MINUTE, m_maxStartTimeAfterTimeSlotInMinutes); + Date maxStartTime = cal.getTime(); + + // find first programm after timeslot + Programme lastPr = null; + while (it.hasNext()) { + Programme pr = it.next(); + + // first check if programm starts at given time + if (pr.start.equals(timeSlot)) { + return pr; + } + + // secondly check if program starts within next 30 minutes + // (configurable via settings) + if (pr.start.after(timeSlot) && pr.start.before(maxStartTime)) { + return pr; + } + + // secondly check if last Programme is still running + if (lastPr != null) { + if (lastPr.stop.after(timeSlot)) { + return lastPr; + } + } + + lastPr = pr; + } + return null; + } + +} diff --git a/src/org/tvheadend/tvhguide/EPGTimeListAdapter.java b/src/org/tvheadend/tvhguide/EPGTimeListAdapter.java new file mode 100644 index 0000000..9ccfc69 --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGTimeListAdapter.java @@ -0,0 +1,86 @@ +package org.tvheadend.tvhguide; + +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.Programme; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +class EPGTimeListAdapter extends ArrayAdapter { + + Activity context; + List list; + Date timeSlot; + + EPGTimeListAdapter(Activity context, List list, Date timeSlot) { + super(context, R.layout.epgnow_list_widget, list); + this.context = context; + this.list = list; + this.timeSlot = timeSlot; + } + + public void sort() { + sort(new Comparator() { + + public int compare(Channel x, Channel y) { + return x.compareTo(y); + } + }); + } + + public Programme getProgrammeAt(int position) { + Channel item = getItem(position); + return ((EPGTimeListActivity) getContext()).getProgrammeStartingAfter( + item, timeSlot); + } + + public void updateView(ListView listView, Channel channel) { + for (int i = 0; i < listView.getChildCount(); i++) { + View view = listView.getChildAt(i); + int pos = listView.getPositionForView(view); + Channel pr = (Channel) listView.getItemAtPosition(pos); + + if (view.getTag() == null || pr == null) { + continue; + } + + if (channel.id != pr.id) { + continue; + } + + EPGTimeListViewWrapper wrapper = (EPGTimeListViewWrapper) view + .getTag(); + wrapper.repaint(channel); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + EPGTimeListViewWrapper wrapper = null; + + if (row == null) { + LayoutInflater inflater = context.getLayoutInflater(); + row = inflater.inflate(R.layout.epgnow_list_widget, null, false); + + wrapper = new EPGTimeListViewWrapper(context, row, timeSlot); + row.setTag(wrapper); + + } else { + wrapper = (EPGTimeListViewWrapper) row.getTag(); + } + + Channel channel = getItem(position); + wrapper.repaint(channel); + return row; + } + +} \ No newline at end of file diff --git a/src/org/tvheadend/tvhguide/EPGTimeListViewWrapper.java b/src/org/tvheadend/tvhguide/EPGTimeListViewWrapper.java new file mode 100644 index 0000000..2aad359 --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGTimeListViewWrapper.java @@ -0,0 +1,84 @@ +package org.tvheadend.tvhguide; + +import java.util.Date; + +import org.tvheadend.tvhguide.htsp.HTSService; +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.Programme; + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.drawable.BitmapDrawable; +import android.preference.PreferenceManager; +import android.view.View; +import android.widget.ImageView; +import android.widget.ProgressBar; + +public class EPGTimeListViewWrapper extends ProgrammeListViewWrapper { + + private final Date timeSlot; + private ImageView icon; + private ProgressBar progressbar; + private final Activity context; + + public EPGTimeListViewWrapper(Activity context, View base, Date timeSlot) { + super(base); + this.context = context; + + icon = (ImageView) base.findViewById(R.id.ch_icon); + progressbar = (ProgressBar) base.findViewById(R.id.ct_loading); + + this.timeSlot = timeSlot; + } + + @SuppressWarnings("deprecation") + public void repaint(Channel channel) { + + if (icon != null) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(icon.getContext()); + Boolean showIcons = prefs.getBoolean("showIconPref", false); + icon.setVisibility(showIcons ? ImageView.VISIBLE : ImageView.GONE); + icon.setBackgroundDrawable(new BitmapDrawable(channel.iconBitmap)); + icon.invalidate(); + } + + Programme pr = ((EPGTimeListActivity) context) + .getProgrammeStartingAfter(channel, timeSlot); + progressbar.setVisibility(ProgressBar.GONE); + if (!channel.isTransmitting || channel.epg.size() == 0) { + title.setText(R.string.ch_no_transmission); + } else if (pr == null) { + // if last, preload next programmes of this channel + pr = channel.epg.last(); + long nextId = pr.nextId; + if (nextId == 0) { + nextId = pr.id; + } + + long diff = timeSlot.getTime() - pr.stop.getTime(); + int hoursDiff = (int) (diff / (1000 * 60 * 60)); + if (hoursDiff < 0) { + hoursDiff = hoursDiff * -1; + } + if (hoursDiff == 0) { + hoursDiff = 1; + } + // load events for the different of hours + Intent intent = new Intent(context, HTSService.class); + intent.setAction(HTSService.ACTION_GET_EVENTS); + intent.putExtra("eventId", nextId); + intent.putExtra("channelId", channel.id); + intent.putExtra("count", hoursDiff + 1); + context.startService(intent); + + // show loading + progressbar.setVisibility(ProgressBar.VISIBLE); + } else { + super.repaint(pr); + } + progressbar.invalidate(); + } + +} diff --git a/src/org/tvheadend/tvhguide/EPGTimelineActivity.java b/src/org/tvheadend/tvhguide/EPGTimelineActivity.java new file mode 100644 index 0000000..da2553b --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGTimelineActivity.java @@ -0,0 +1,413 @@ +package org.tvheadend.tvhguide; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.tvheadend.tvhguide.htsp.HTSListener; +import org.tvheadend.tvhguide.htsp.HTSService; +import org.tvheadend.tvhguide.intent.SearchEPGIntent; +import org.tvheadend.tvhguide.intent.SearchIMDbIntent; +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.ChannelTag; +import org.tvheadend.tvhguide.model.Programme; +import org.tvheadend.tvhguide.model.Recording; +import org.tvheadend.tvhguide.ui.HorizontalListView; + +import android.app.AlertDialog; +import android.app.ListActivity; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.drawable.BitmapDrawable; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; + +/** + * + * @author mike toggweiler + * + */ +public class EPGTimelineActivity extends ListActivity implements HTSListener { + + private EPGTimelineAdapter adapter; + + private AlertDialog tagDialog; + private ArrayAdapter tagAdapter; + + private List mListeners = Collections + .synchronizedList(new ArrayList()); + + private int mLastEPGScrollPosition; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + adapter = new EPGTimelineAdapter(this, new ArrayList()); + setListAdapter(adapter); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.menu_tags); + + tagAdapter = new ArrayAdapter(this, + android.R.layout.simple_dropdown_item_1line, + new ArrayList()); + + builder.setAdapter(tagAdapter, + new android.content.DialogInterface.OnClickListener() { + + public void onClick(DialogInterface arg0, int pos) { + ChannelTag tag = tagAdapter.getItem(pos); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.setCurrentTag(tag); + + setCurrentTag(tag); + populateChannelList(); + } + }); + + tagDialog = builder.create(); + } + + private void setCurrentTag(ChannelTag t) { + if (getActionBar() != null) { + + if (t == null) { + getActionBar().setTitle(R.string.pr_all_channels); + getActionBar().setIcon(R.drawable.logo_72); + } else { + getActionBar().setTitle(t.name); + getActionBar().setIcon(R.drawable.logo_72); + + if (t.iconBitmap != null) { + getActionBar().setIcon( + new BitmapDrawable(getResources(), t.iconBitmap)); + } else { + getActionBar().setIcon(R.drawable.logo_72); + } + } + } + } + + public void populateChannelList() { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + ChannelTag currentTag = app.getCurrentTag(); + adapter.clear(); + // mListeners.clear(); + + adapter.add(new DummyChannel()); + for (Channel ch : app.getChannels()) { + if (currentTag == null || ch.hasTag(currentTag.id)) { + adapter.add(ch); + } + } + + adapter.sort(); + adapter.notifyDataSetChanged(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.main_menu, menu); + return true; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + if (info == null) { + return; + } + + HorizontalListView hv = (HorizontalListView) v; + Programme p = (Programme) hv.getItemAtPosition(info.position); + + if (p == null) { + return; + } + + menu.setHeaderTitle(p.title); + + Intent intent = new Intent(this, HTSService.class); + + MenuItem item = null; + + if (p != null) { + if (p.recording == null) { + intent.setAction(HTSService.ACTION_DVR_ADD); + intent.putExtra("eventId", p.id); + intent.putExtra("channelId", p.channel.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record, + ContextMenu.NONE, R.string.menu_record); + } else if (p.isRecording() || p.isScheduled()) { + intent.setAction(HTSService.ACTION_DVR_CANCEL); + intent.putExtra("id", p.recording.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record_cancel, + ContextMenu.NONE, R.string.menu_record_cancel); + } else { + intent.setAction(HTSService.ACTION_DVR_DELETE); + intent.putExtra("id", p.recording.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record_remove, + ContextMenu.NONE, R.string.menu_record_remove); + } + + item.setIntent(intent); + + item = menu.add(ContextMenu.NONE, R.string.search_hint, + ContextMenu.NONE, R.string.search_hint); + item.setIntent(new SearchEPGIntent(this, p.title)); + + item = menu.add(ContextMenu.NONE, ContextMenu.NONE, + ContextMenu.NONE, "IMDb"); + item.setIntent(new SearchIMDbIntent(this, p.title)); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.string.menu_record: + case R.string.menu_record_cancel: + case R.string.menu_record_remove: { + startService(item.getIntent()); + return true; + } + default: { + return super.onContextItemSelected(item); + } + } + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.mi_settings: { + Intent intent = new Intent(getBaseContext(), SettingsActivity.class); + startActivityForResult(intent, R.id.mi_settings); + return true; + } + // case R.id.mi_refresh: { + // connect(true); + // return true; + // } + case R.id.mi_recordings: { + Intent intent = new Intent(getBaseContext(), + RecordingListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_epg_prime: { + Intent intent = new Intent(getBaseContext(), + EPGPrimeTimeListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_epg_list: { + Intent intent = new Intent(getBaseContext(), + EPGHourlyTimeListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_channels: { + Intent intent = new Intent(getBaseContext(), + ChannelListActivity.class); + startActivity(intent); + return true; + } + case R.id.mi_epg_timeline: { + return true; + } + case R.id.mi_search: { + onSearchRequested(); + return true; + } + case R.id.mi_tags: { + tagDialog.show(); + return true; + } + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + protected void onResume() { + super.onResume(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.addListener(this); + + setLoading(app.isLoading()); + } + + @Override + protected void onPause() { + super.onPause(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.removeListener(this); + } + + @Override + public void onMessage(String action, final Object obj) { + if (action.equals(TVHGuideApplication.ACTION_LOADING)) { + + runOnUiThread(new Runnable() { + + public void run() { + boolean loading = (Boolean) obj; + setLoading(loading); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_ADD)) { + runOnUiThread(new Runnable() { + + public void run() { + adapter.add((Channel) obj); + adapter.notifyDataSetChanged(); + adapter.sort(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_DELETE)) { + runOnUiThread(new Runnable() { + + public void run() { + adapter.remove((Channel) obj); + adapter.notifyDataSetChanged(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_CHANNEL_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Channel channel = (Channel) obj; + adapter.updateView(getListView(), channel); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_TAG_ADD)) { + runOnUiThread(new Runnable() { + + public void run() { + ChannelTag tag = (ChannelTag) obj; + tagAdapter.add(tag); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_TAG_DELETE)) { + runOnUiThread(new Runnable() { + + public void run() { + ChannelTag tag = (ChannelTag) obj; + tagAdapter.remove(tag); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_ADD)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + try { + adapter.updateView(getListView(), p.channel); + } catch (Exception e) { + } + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_DELETE)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + adapter.updateView(getListView(), p.channel); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + adapter.updateView(getListView(), p.channel); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_DVR_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Recording rec = (Recording) obj; + for (Channel c : adapter.list) { + for (Programme p : c.epg) { + if (rec == p.recording) { + adapter.updateView(getListView(), c); + return; + } + } + } + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_TAG_UPDATE)) { + // NOP + } + } + + private void setLoading(boolean loading) { + if (loading) { + // + } else { + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + tagAdapter.clear(); + for (ChannelTag t : app.getChannelTags()) { + tagAdapter.add(t); + } + setCurrentTag(app.getCurrentTag()); + + populateChannelList(); + } + } + + public void notifyOnScoll(int scrollTo) { + mLastEPGScrollPosition = scrollTo; + for (OnEPGScrollListener listener : mListeners) { + listener.scrollTo(scrollTo); + } + } + + public int getLastEPGScrollPosition() { + return mLastEPGScrollPosition; + } + + public void notifyOnFling(float velocityX) { + for (OnEPGScrollListener listener : mListeners) { + listener.flingBy(velocityX); + } + } + + public void registerOnScrollListener(OnEPGScrollListener listener) { + mListeners.add(listener); + } + + public void unregisterOnScrollListener(OnEPGScrollListener listener) { + mListeners.remove(listener); + } + + public static interface OnEPGScrollListener { + + public void scrollTo(int scrollTo); + + public void flingBy(float velocityX); + } + + public static class DummyChannel extends Channel { + public DummyChannel() { + id = 0; + number = -1; + } + } +} diff --git a/src/org/tvheadend/tvhguide/EPGTimelineAdapter.java b/src/org/tvheadend/tvhguide/EPGTimelineAdapter.java new file mode 100644 index 0000000..59cf2ee --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGTimelineAdapter.java @@ -0,0 +1,158 @@ +package org.tvheadend.tvhguide; + +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import org.tvheadend.tvhguide.htsp.HTSService; +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.Programme; + +import android.app.Activity; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; + +class EPGTimelineAdapter extends ArrayAdapter implements + EventLoadHandler { + + private final EPGTimelineActivity context; + final List list; + private boolean mLocked; + + EPGTimelineAdapter(EPGTimelineActivity context, List list) { + super(context, R.layout.epgtimeline_widget, list); + this.context = context; + this.list = list; + } + + public void sort() { + sort(new Comparator() { + + public int compare(Channel x, Channel y) { + if (x == null) { + return -1; + } + return x.compareTo(y); + } + }); + } + + public void updateView(ListView listView, Channel channel) { + + if (channel == null) { + // update header timerline + View view = listView.getChildAt(0); + EPGTimelineViewWrapper wrapper = (EPGTimelineViewWrapper) view + .getTag(); + wrapper.repaint(channel); + return; + } + + for (int i = 0; i < listView.getChildCount(); i++) { + View view = listView.getChildAt(i); + int pos = listView.getPositionForView(view); + Channel ch = (Channel) listView.getItemAtPosition(pos); + + if (view.getTag() == null || ch == null) { + continue; + } + + if (channel.id != ch.id) { + continue; + } + + EPGTimelineViewWrapper wrapper = (EPGTimelineViewWrapper) view + .getTag(); + wrapper.repaint(channel); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + EPGTimelineViewWrapper wrapper; + + Channel ch = getItem(position); + Activity activity = (Activity) getContext(); + + if (row == null) { + LayoutInflater inflater = activity.getLayoutInflater(); + row = inflater.inflate(R.layout.epgtimeline_widget, null, false); + row.requestLayout(); + wrapper = new EPGTimelineViewWrapper(context, row, this); + context.registerOnScrollListener(wrapper); + row.setTag(wrapper); + + } else { + wrapper = (EPGTimelineViewWrapper) row.getTag(); + } + + wrapper.repaint(ch); + return row; + } + + @Override + public void loadNextEvents() { + if (mLocked) { + return; + } + mLocked = true; + try { + // get latest loaded programme + Date latestStop = null; + for (int i = 0; i < getCount(); ++i) { + + Channel ch = getItem(i); + if (ch.epg.size() > 0) { + Programme last = ch.epg.last(); + if (latestStop == null || latestStop.before(last.stop)) { + latestStop = last.stop; + } + } + } + + for (int i = 0; i < getCount(); ++i) { + + Channel ch = getItem(i); + if (ch.epg.size() > 0) { + Programme last = ch.epg.last(); + long nextId = last.nextId; + if (nextId == 0) { + nextId = last.id; + } + + // load per hour an event to difference of latest loaded + // stop + // program + long diff = latestStop.getTime() - last.stop.getTime(); + int hoursDiff = (int) (diff / (1000 * 60 * 60)); + if (hoursDiff < 0) { + hoursDiff = hoursDiff * -1; + } + if (hoursDiff == 0) { + hoursDiff = 1; + } + + Intent intent = new Intent(context, HTSService.class); + intent.setAction(HTSService.ACTION_GET_EVENTS); + intent.putExtra("eventId", nextId); + intent.putExtra("channelId", ch.id); + intent.putExtra("count", hoursDiff + 5); + context.startService(intent); + } + } + } finally { + mLocked = false; + } + } + + @Override + public void clear() { + + super.clear(); + } +} \ No newline at end of file diff --git a/src/org/tvheadend/tvhguide/EPGTimelineProgrammeHeaderViewWrapper.java b/src/org/tvheadend/tvhguide/EPGTimelineProgrammeHeaderViewWrapper.java new file mode 100644 index 0000000..46930d9 --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGTimelineProgrammeHeaderViewWrapper.java @@ -0,0 +1,57 @@ +package org.tvheadend.tvhguide; + +import java.util.Calendar; +import java.util.Date; + +import android.content.Context; +import android.text.format.DateFormat; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class EPGTimelineProgrammeHeaderViewWrapper { + TextView date; + TextView hour; + private LinearLayout container; + private java.text.DateFormat timeFormat; + private java.text.DateFormat dateFormat; + + public EPGTimelineProgrammeHeaderViewWrapper(Context context, View base) { + date = (TextView) base.findViewById(R.id.title_date); + hour = (TextView) base.findViewById(R.id.title_hour); + container = (LinearLayout) base.findViewById(R.id.programme_container); + timeFormat = DateFormat.getTimeFormat(context); + dateFormat = DateFormat.getDateFormat(context); + } + + public void repaint(Date dt) { + date.setText(dateFormat.format(dt)); + hour.setText(timeFormat.format(dt)); + + // calculate remaining minutes till next hour + Calendar cal = Calendar.getInstance(); + cal.setTime(dt); + cal.add(Calendar.HOUR_OF_DAY, 1); + cal.clear(Calendar.MINUTE); + cal.clear(Calendar.SECOND); + cal.clear(Calendar.MILLISECOND); + + long remainingMillis = cal.getTimeInMillis() - dt.getTime(); + long minutes = remainingMillis / (60 * 1000); + + int width = (int) (minutes * EPGTimelineViewWrapper.WIDTH_PER_MINUTE); + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( + width, 45); + LinearLayout.LayoutParams layoutParams2 = new LinearLayout.LayoutParams( + width, LinearLayout.LayoutParams.WRAP_CONTENT); + container.setLayoutParams(layoutParams); + container.setVisibility(LinearLayout.VISIBLE); + + date.setLayoutParams(layoutParams2); + hour.setLayoutParams(layoutParams2); + + date.invalidate(); + hour.invalidate(); + container.invalidate(); + } +} diff --git a/src/org/tvheadend/tvhguide/EPGTimelineProgrammeListViewWrapper.java b/src/org/tvheadend/tvhguide/EPGTimelineProgrammeListViewWrapper.java new file mode 100644 index 0000000..37dbf7e --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGTimelineProgrammeListViewWrapper.java @@ -0,0 +1,98 @@ +package org.tvheadend.tvhguide; + +import java.util.Calendar; +import java.util.Date; + +import org.tvheadend.tvhguide.model.Programme; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.util.Log; +import android.view.View; +import android.widget.LinearLayout; + +public class EPGTimelineProgrammeListViewWrapper extends + ProgrammeListViewWrapper { + + private TypedArray colors; + private LinearLayout container; + private LinearLayout container2; + private LinearLayout container3; + private static final int LAYOUT_HEIGHT = 68; + + private static final String TAG = "EPGTimelineProgrammeListViewWrapper"; + + public EPGTimelineProgrammeListViewWrapper(View base) { + super(base); + Resources res = base.getResources(); + colors = res.obtainTypedArray(R.array.pref_color_content_type); + // colors.recycle(); + + container = (LinearLayout) base.findViewById(R.id.programme_container); + container2 = (LinearLayout) base + .findViewById(R.id.programme_container2); + container3 = (LinearLayout) base + .findViewById(R.id.programme_container3); + } + + @Override + public void repaint(Programme p) { + super.repaint(p); + + // colorize based on series category + // the first byte of hex number represents the main category + int type = 0; + if (p.contentType > 0) { + type = ((p.contentType) / 16) - 1; + } + // there are 11 categories, calculate modulo if more categories are + // returned than colors are defined + int index = type % colors.length(); + int color; + try { + color = colors.getColor(index, 0); + } catch (Exception e) { + Log.d(TAG, "Didn't find color for index:" + index + ", type:" + + type); + color = Color.BLACK; + } + + // use first byte of hex number to calculate color offset + int subType = 0; + if (type > 0) { + subType = p.contentType & 0x0F; + color -= subType * 0x040404; + } + + Log.v(TAG, p.title + ", content-type:" + p.contentType + ", type:" + + type + ", subtype:" + subType + ", index:" + index + + ", color:" + color); + container.setBackgroundColor(color); + + // define width based on duration + Date start = Calendar.getInstance().getTime(); + if (p.stop.after(start)) { + if (p.start.after(start)) { + start = p.start; + } + + long remainingMillis = p.stop.getTime() - start.getTime(); + long minutes = remainingMillis / (60 * 1000); + + LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( + (int) minutes * EPGTimelineViewWrapper.WIDTH_PER_MINUTE, + LAYOUT_HEIGHT); + container.setLayoutParams(layoutParams); + container.setVisibility(LinearLayout.VISIBLE); + + container2.setLayoutParams(layoutParams); + container3.setLayoutParams(layoutParams); + } else { + container.setVisibility(LinearLayout.GONE); + } + container.invalidate(); + container2.invalidate(); + container3.invalidate(); + } +} diff --git a/src/org/tvheadend/tvhguide/EPGTimelineViewWrapper.java b/src/org/tvheadend/tvhguide/EPGTimelineViewWrapper.java new file mode 100644 index 0000000..4064d4a --- /dev/null +++ b/src/org/tvheadend/tvhguide/EPGTimelineViewWrapper.java @@ -0,0 +1,329 @@ +/* + * Copyright (C) 2011 John Törnblom + * + * This file is part of TVHGuide. + * + * TVHGuide 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. + * + * TVHGuide 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 TVHGuide. If not, see . + */ +package org.tvheadend.tvhguide; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +import org.tvheadend.tvhguide.EPGTimelineActivity.OnEPGScrollListener; +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.Programme; +import org.tvheadend.tvhguide.ui.HorizontalListView; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.BitmapDrawable; +import android.view.GestureDetector; +import android.view.GestureDetector.OnGestureListener; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ProgressBar; + +/** + * + * @author mike toggweiler + */ +public class EPGTimelineViewWrapper implements OnItemClickListener, + OnTouchListener, OnEPGScrollListener { + + private ImageView icon; + private final EPGTimelineActivity context; + private HorizontalListView horizontalListView; + private boolean locked; + + private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() { + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + try { + locked = true; + context.notifyOnScoll(horizontalListView.getScrollPositionX()); + } finally { + locked = false; + } + return false; + } + + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + try { + locked = true; + context.notifyOnFling(velocityX); + } finally { + locked = false; + } + return false; + }; + }; + private GestureDetector mGesture; + private final EventLoadHandler loadHandler; + static final int WIDTH_PER_MINUTE = 5; + + public EPGTimelineViewWrapper(EPGTimelineActivity context, View base, + EventLoadHandler loadHandler) { + this.context = context; + this.loadHandler = loadHandler; + icon = (ImageView) base.findViewById(R.id.ch_icon); + + mGesture = new GestureDetector(context, mOnGesture); + + horizontalListView = (HorizontalListView) base + .findViewById(R.id.ch_timeline); + horizontalListView.setClickable(true); + horizontalListView.setOnItemClickListener(this); + horizontalListView.setOnTouchListener(this); + + context.registerForContextMenu(horizontalListView); + } + + @SuppressWarnings("deprecation") + public void repaintHeader() { + Calendar cal = Calendar.getInstance(); + List dates = new ArrayList(); + dates.add(cal.getTime()); + + cal.clear(Calendar.MINUTE); + cal.clear(Calendar.SECOND); + cal.clear(Calendar.MILLISECOND); + for (int i = 0; i < 100; ++i) { + cal.add(Calendar.HOUR_OF_DAY, 1); + dates.add(cal.getTime()); + } + + icon.setBackgroundDrawable(null); + icon.setImageDrawable(null); + icon.invalidate(); + + TimelineHeaderAdaper adapter = new TimelineHeaderAdaper(context, dates); + horizontalListView.setAdapter(adapter); + horizontalListView.scrollTo(context.getLastEPGScrollPosition()); + horizontalListView.invalidate(); + } + + @SuppressWarnings("deprecation") + public void repaint(Channel channel) { + + if (channel.id == 0) { + repaintHeader(); + return; + } + + // SharedPreferences prefs = PreferenceManager + // .getDefaultSharedPreferences(icon.getContext()); + // Boolean showIcons = prefs.getBoolean("showIconPref", false); + // icon.setVisibility(showIcons ? ImageView.VISIBLE : ImageView.GONE); + icon.setBackgroundDrawable(new BitmapDrawable(context.getResources(), + channel.iconBitmap)); + + if (channel.isRecording()) { + icon.setImageResource(R.drawable.ic_rec_small); + } else { + icon.setImageDrawable(null); + } + icon.invalidate(); + + TimelineProgrammeAdapter adapter = new TimelineProgrammeAdapter( + context, new ArrayList(channel.epg)); + horizontalListView.setAdapter(adapter); + horizontalListView.scrollTo(context.getLastEPGScrollPosition()); + horizontalListView.invalidate(); + } + + @Override + public boolean onTouch(View view, MotionEvent event) { + mGesture.onTouchEvent(event); + return false; + } + + @Override + public void onItemClick(AdapterView adapterView, View view, + int position, long id) { + Object obj = adapterView.getItemAtPosition(position); + if (obj != null && !(obj instanceof Programme)) { + return; + } + Programme p = (Programme) obj; + + if (p == null) { + // load next + loadHandler.loadNextEvents(); + } else { + Intent intent = new Intent(context, ProgrammeActivity.class); + intent.putExtra("eventId", p.id); + intent.putExtra("channelId", p.channel.id); + context.startActivity(intent); + } + } + + protected void loadNext() { + + } + + @Override + public void scrollTo(int scrollTo) { + if (locked) { + return; + } + synchronized (horizontalListView) { + horizontalListView.scrollTo(scrollTo); + } + } + + @Override + public void flingBy(float velocityX) { + if (locked) { + return; + } + synchronized (horizontalListView) { + horizontalListView.flingBy(velocityX); + } + } + + class TimelineHeaderAdaper extends ArrayAdapter { + TimelineHeaderAdaper(Context context, List epg) { + super(context, R.layout.epgtimeline_programme_header, epg); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + EPGTimelineProgrammeHeaderViewWrapper wrapper; + + Activity activity = (Activity) getContext(); + Date dt = getItem(position); + if (row == null + || !(row.getTag() instanceof EPGTimelineProgrammeHeaderViewWrapper)) { + LayoutInflater inflater = activity.getLayoutInflater(); + row = inflater.inflate(R.layout.epgtimeline_programme_header, + null, false); + row.requestLayout(); + wrapper = new EPGTimelineProgrammeHeaderViewWrapper(context, + row); + row.setTag(wrapper); + + } else { + wrapper = (EPGTimelineProgrammeHeaderViewWrapper) row.getTag(); + } + + if (wrapper != null) { + wrapper.repaint(dt); + } + return row; + } + } + + class TimelineProgrammeAdapter extends ArrayAdapter { + + public static final int VIEW_TYPE_END = 100; + private ProgressBar mProgressbar; + + TimelineProgrammeAdapter(Context context, List epg) { + super(context, R.layout.epgtimeline_programme_widget, epg); + mProgressbar = new ProgressBar(context); + } + + public void sort() { + sort(new Comparator() { + + public int compare(Programme x, Programme y) { + return x.compareTo(y); + } + }); + } + + @Override + public int getCount() { + return super.getCount() + 1; + } + + @Override + public int getItemViewType(int position) { + if (position == super.getCount()) { + return VIEW_TYPE_END; + } + return super.getItemViewType(position); + } + + @Override + public Programme getItem(int position) { + if (position == super.getCount()) { + return null; + } + return super.getItem(position); + } + + @Override + public long getItemId(int position) { + if (position == super.getCount()) { + return VIEW_TYPE_END; + } + return super.getItemId(position); + } + + @Override + public int getViewTypeCount() { + return super.getViewTypeCount() + 1; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + EPGTimelineProgrammeListViewWrapper wrapper; + + if (position == super.getCount()) { + loadHandler.loadNextEvents(); + return mProgressbar; + } else { + Programme pr = getItem(position); + Activity activity = (Activity) getContext(); + + if (row == null + || !(row.getTag() instanceof EPGTimelineProgrammeListViewWrapper)) { + LayoutInflater inflater = activity.getLayoutInflater(); + row = inflater.inflate( + R.layout.epgtimeline_programme_widget, null, false); + row.requestLayout(); + wrapper = new EPGTimelineProgrammeListViewWrapper(row); + row.setTag(wrapper); + + } else { + wrapper = (EPGTimelineProgrammeListViewWrapper) row + .getTag(); + } + + if (wrapper != null) { + wrapper.repaint(pr); + } + return row; + } + } + } +} diff --git a/src/org/tvheadend/tvhguide/EventLoadHandler.java b/src/org/tvheadend/tvhguide/EventLoadHandler.java new file mode 100644 index 0000000..97574aa --- /dev/null +++ b/src/org/tvheadend/tvhguide/EventLoadHandler.java @@ -0,0 +1,5 @@ +package org.tvheadend.tvhguide; + +public interface EventLoadHandler { + public void loadNextEvents(); +} \ No newline at end of file diff --git a/src/org/tvheadend/tvhguide/ProgrammeActivity.java b/src/org/tvheadend/tvhguide/ProgrammeActivity.java index 90a983d..305d409 100644 --- a/src/org/tvheadend/tvhguide/ProgrammeActivity.java +++ b/src/org/tvheadend/tvhguide/ProgrammeActivity.java @@ -18,150 +18,168 @@ */ package org.tvheadend.tvhguide; +import java.util.Locale; + +import org.tvheadend.tvhguide.R.string; +import org.tvheadend.tvhguide.htsp.HTSListener; +import org.tvheadend.tvhguide.htsp.HTSService; +import org.tvheadend.tvhguide.intent.SearchEPGIntent; +import org.tvheadend.tvhguide.intent.SearchIMDbIntent; +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.Programme; +import org.tvheadend.tvhguide.model.SeriesInfo; + import android.app.Activity; import android.content.Intent; -import android.content.SharedPreferences; +import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; -import android.preference.PreferenceManager; import android.text.format.DateFormat; import android.util.SparseArray; import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.Window; import android.widget.ImageView; import android.widget.RatingBar; import android.widget.TextView; -import org.tvheadend.tvhguide.R; -import org.tvheadend.tvhguide.R.string; -import org.tvheadend.tvhguide.htsp.HTSService; -import org.tvheadend.tvhguide.intent.SearchEPGIntent; -import org.tvheadend.tvhguide.intent.SearchIMDbIntent; -import org.tvheadend.tvhguide.model.Channel; -import org.tvheadend.tvhguide.model.Programme; -import org.tvheadend.tvhguide.model.SeriesInfo; - /** - * + * * @author john-tornblom */ -public class ProgrammeActivity extends Activity { - - private Programme programme; - - @Override - public void onCreate(Bundle savedInstanceState) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - Boolean theme = prefs.getBoolean("lightThemePref", false); - setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme); - - super.onCreate(savedInstanceState); - - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Channel channel = app.getChannel(getIntent().getLongExtra("channelId", 0)); - if (channel == null) { - finish(); - return; - } - - long eventId = getIntent().getLongExtra("eventId", 0); - for (Programme p : channel.epg) { - if (p.id == eventId) { - programme = p; - break; - } - } - - if (programme == null) { - finish(); - return; - } - - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - - setContentView(R.layout.programme_layout); - - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.programme_title); - TextView t = (TextView) findViewById(R.id.ct_title); - t.setText(channel.name); - - if (channel.iconBitmap != null) { - ImageView iv = (ImageView) findViewById(R.id.ct_logo); - iv.setImageBitmap(channel.iconBitmap); - } - - - TextView text = (TextView) findViewById(R.id.pr_title); - text.setText(programme.title); - - text = (TextView) findViewById(R.id.pr_channel); - text.setText(channel.name); - - text = (TextView) findViewById(R.id.pr_airing); - text.setText( - DateFormat.getLongDateFormat(text.getContext()).format(programme.start) - + " " - + DateFormat.getTimeFormat(text.getContext()).format(programme.start) - + " - " - + DateFormat.getTimeFormat(text.getContext()).format(programme.stop)); - - - if(programme.summary.length() == 0 && programme.description.length() == 0) { - View v = findViewById(R.id.pr_summay_and_desc_layout); - v.setVisibility(View.GONE); - } else { - text = (TextView) findViewById(R.id.pr_summary); - text.setText(programme.summary); - if(programme.summary.length() == 0) - text.setVisibility(View.GONE); - - text = (TextView) findViewById(R.id.pr_desc); - text.setText(programme.description); - if(programme.description.length() == 0) - text.setVisibility(View.GONE); - } - - String s = buildSeriesInfoString(programme.seriesInfo); - if(s.length() > 0) { - text = (TextView) findViewById(R.id.pr_series_info); - text.setText(s); - } else { - View v = findViewById(R.id.pr_series_info_row); - v.setVisibility(View.GONE); - v = findViewById(R.id.pr_series_info_sep); - v.setVisibility(View.GONE); - } - - SparseArray contentTypes = TVHGuideApplication.getContentTypes(this); - s = contentTypes.get(programme.contentType, ""); - if(s.length() > 0) { - text = (TextView) findViewById(R.id.pr_content_type); - text.setText(s); - } else { - View v = findViewById(R.id.pr_content_type_row); - v.setVisibility(View.GONE); - v = findViewById(R.id.pr_content_type_sep); - v.setVisibility(View.GONE); - } - - if(programme.starRating > 0) { - RatingBar starRating = (RatingBar)findViewById(R.id.pr_star_rating); - starRating.setRating((float)programme.starRating / 10.0f); - - text = (TextView) findViewById(R.id.pr_star_rating_txt); - text.setText("(" - + programme.starRating - + "/" - + 100 - + ")"); - } else { - View v = findViewById(R.id.pr_star_rating_row); - v.setVisibility(View.GONE); - } - } - - +public class ProgrammeActivity extends Activity implements HTSListener { + + private Programme programme; + private ImageView state; + private Channel channel; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + channel = app.getChannel(getIntent().getLongExtra("channelId", 0)); + if (channel == null) { + finish(); + return; + } + + long eventId = getIntent().getLongExtra("eventId", 0); + for (Programme p : channel.epg) { + if (p.id == eventId) { + programme = p; + break; + } + } + + if (programme == null) { + finish(); + return; + } + + setContentView(R.layout.programme_layout); + + TextView text = (TextView) findViewById(R.id.pr_title); + text.setText(programme.title); + + text = (TextView) findViewById(R.id.pr_channel); + text.setText(channel.name); + + text = (TextView) findViewById(R.id.pr_airing); + text.setText(DateFormat.getLongDateFormat(text.getContext()).format( + programme.start) + + " " + + DateFormat.getTimeFormat(text.getContext()).format( + programme.start) + + " - " + + DateFormat.getTimeFormat(text.getContext()).format( + programme.stop)); + + if (programme.summary.length() == 0 + && programme.description.length() == 0) { + View v = findViewById(R.id.pr_summay_and_desc_layout); + v.setVisibility(View.GONE); + } else { + text = (TextView) findViewById(R.id.pr_summary); + text.setText(programme.summary); + if (programme.summary.length() == 0) + text.setVisibility(View.GONE); + + text = (TextView) findViewById(R.id.pr_desc); + text.setText(programme.description); + if (programme.description.length() == 0) + text.setVisibility(View.GONE); + } + + String s = buildSeriesInfoString(programme.seriesInfo); + if (s.length() > 0) { + text = (TextView) findViewById(R.id.pr_series_info); + text.setText(s); + } else { + View v = findViewById(R.id.pr_series_info_row); + v.setVisibility(View.GONE); + v = findViewById(R.id.pr_series_info_sep); + v.setVisibility(View.GONE); + } + + SparseArray contentTypes = TVHGuideApplication + .getContentTypes(this.getResources()); + s = contentTypes.get(programme.contentType, ""); + if (s.length() > 0) { + text = (TextView) findViewById(R.id.pr_content_type); + text.setText(s); + } else { + View v = findViewById(R.id.pr_content_type_row); + v.setVisibility(View.GONE); + v = findViewById(R.id.pr_content_type_sep); + v.setVisibility(View.GONE); + } + + if (programme.starRating > 0) { + RatingBar starRating = (RatingBar) findViewById(R.id.pr_star_rating); + starRating.setRating((float) programme.starRating / 10.0f); + + text = (TextView) findViewById(R.id.pr_star_rating_txt); + text.setText("(" + programme.starRating + "/" + 100 + ")"); + } else { + View v = findViewById(R.id.pr_star_rating_row); + v.setVisibility(View.GONE); + } + + state = (ImageView) findViewById(R.id.pr_state); + update(programme); + } + + protected void update(Programme p) { + RecordUtil.applyRecording(p.recording, state); + } + + @Override + protected void onResume() { + super.onResume(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.addListener(this); + } + + @Override + protected void onPause() { + super.onPause(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.removeListener(this); + } + + @Override + public void onMessage(String action, final Object obj) { + if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + update(p); + } + }); + } + } + public String buildSeriesInfoString(SeriesInfo info) { if (info.onScreen != null && info.onScreen.length() > 0) return info.onScreen; @@ -170,108 +188,126 @@ public String buildSeriesInfoString(SeriesInfo info) { String season = this.getResources().getString(string.pr_season); String episode = this.getResources().getString(string.pr_episode); String part = this.getResources().getString(string.pr_part); - - if(info.onScreen.length() > 0) { + + if (info.onScreen.length() > 0) { return info.onScreen; } - + if (info.seasonNumber > 0) { if (s.length() > 0) s += ", "; - s += String.format("%s %02d", season.toLowerCase(), info.seasonNumber); - if(info.seasonCount > 0) + s += String.format("%s %02d", + season.toLowerCase(Locale.getDefault()), info.seasonNumber); + if (info.seasonCount > 0) s += String.format("/%02d", info.seasonCount); } if (info.episodeNumber > 0) { if (s.length() > 0) s += ", "; - s += String.format("%s %02d", episode.toLowerCase(), info.episodeNumber); - if(info.episodeCount > 0) + s += String.format("%s %02d", + episode.toLowerCase(Locale.getDefault()), + info.episodeNumber); + if (info.episodeCount > 0) s += String.format("/%02d", info.episodeCount); } if (info.partNumber > 0) { if (s.length() > 0) s += ", "; - s += String.format("%s %d", part.toLowerCase(), info.partNumber); - if(info.partCount > 0) + s += String.format("%s %d", part.toLowerCase(Locale.getDefault()), + info.partNumber); + if (info.partCount > 0) s += String.format("/%02d", info.partCount); } - if(s.length() > 0) { - s = s.substring(0,1).toUpperCase() + s.substring(1); + if (s.length() > 0) { + s = s.substring(0, 1).toUpperCase(Locale.getDefault()) + + s.substring(1); } - + return s; } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuItem item = null; - - if (programme.title != null) { - item = menu.add(Menu.NONE, android.R.string.search_go, Menu.NONE, android.R.string.search_go); - item.setIntent(new SearchEPGIntent(this, programme.title)); - item.setIcon(android.R.drawable.ic_menu_search); - - item = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, "IMDb"); - item.setIntent(new SearchIMDbIntent(this, programme.title)); - item.setIcon(android.R.drawable.ic_menu_info_details); - } - - Intent intent = new Intent(this, HTSService.class); - - if (programme.recording == null) { - intent.setAction(HTSService.ACTION_DVR_ADD); - intent.putExtra("eventId", programme.id); - intent.putExtra("channelId", programme.channel.id); - item = menu.add(Menu.NONE, R.string.menu_record, Menu.NONE, R.string.menu_record); - item.setIcon(android.R.drawable.ic_menu_save); - } else if (programme.isRecording() || programme.isScheduled()) { - intent.setAction(HTSService.ACTION_DVR_CANCEL); - intent.putExtra("id", programme.recording.id); - item = menu.add(Menu.NONE, R.string.menu_record_cancel, Menu.NONE, R.string.menu_record_cancel); - item.setIcon(android.R.drawable.ic_menu_close_clear_cancel); - } else { - intent.setAction(HTSService.ACTION_DVR_DELETE); - intent.putExtra("id", programme.recording.id); - item = menu.add(Menu.NONE, R.string.menu_record_remove, Menu.NONE, R.string.menu_record_remove); - item.setIcon(android.R.drawable.ic_menu_delete); - } - - item.setIntent(intent); - - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - boolean rebuild = false; - if (programme.recording == null) { - rebuild = menu.findItem(R.string.menu_record) == null; - } else if (programme.isRecording() || programme.isScheduled()) { - rebuild = menu.findItem(R.string.menu_record_cancel) == null; - } else { - rebuild = menu.findItem(R.string.menu_record_remove) == null; - } - - if (rebuild) { - menu.clear(); - return onCreateOptionsMenu(menu); - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.string.menu_record_remove: - case R.string.menu_record_cancel: - case R.string.menu_record: - startService(item.getIntent()); - return true; - default: - return super.onOptionsItemSelected(item); - } - } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuItem item = null; + + if (programme.title != null) { + item = menu.add(Menu.NONE, android.R.string.search_go, Menu.NONE, + android.R.string.search_go); + item.setIntent(new SearchEPGIntent(this, programme.title)); + item.setIcon(android.R.drawable.ic_menu_search); + + item = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, "IMDb"); + item.setIntent(new SearchIMDbIntent(this, programme.title)); + item.setIcon(android.R.drawable.ic_menu_info_details); + } + + Intent intent = new Intent(this, HTSService.class); + + if (programme.recording == null) { + intent.setAction(HTSService.ACTION_DVR_ADD); + intent.putExtra("eventId", programme.id); + intent.putExtra("channelId", programme.channel.id); + item = menu.add(Menu.NONE, R.string.menu_record, Menu.NONE, + R.string.menu_record); + item.setIcon(android.R.drawable.ic_menu_save); + } else if (programme.isRecording() || programme.isScheduled()) { + intent.setAction(HTSService.ACTION_DVR_CANCEL); + intent.putExtra("id", programme.recording.id); + item = menu.add(Menu.NONE, R.string.menu_record_cancel, Menu.NONE, + R.string.menu_record_cancel); + item.setIcon(android.R.drawable.ic_menu_close_clear_cancel); + } else { + intent.setAction(HTSService.ACTION_DVR_DELETE); + intent.putExtra("id", programme.recording.id); + item = menu.add(Menu.NONE, R.string.menu_record_remove, Menu.NONE, + R.string.menu_record_remove); + item.setIcon(android.R.drawable.ic_menu_delete); + } + + if (getActionBar() != null && channel != null) { + getActionBar().setTitle(channel.name); + + if (channel.iconBitmap != null) { + getActionBar().setIcon( + new BitmapDrawable(getResources(), channel.iconBitmap)); + } + } + + item.setIntent(intent); + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + boolean rebuild = false; + if (programme.recording == null) { + rebuild = menu.findItem(R.string.menu_record) == null; + } else if (programme.isRecording() || programme.isScheduled()) { + rebuild = menu.findItem(R.string.menu_record_cancel) == null; + } else { + rebuild = menu.findItem(R.string.menu_record_remove) == null; + } + + if (rebuild) { + menu.clear(); + return onCreateOptionsMenu(menu); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.string.menu_record_remove: + case R.string.menu_record_cancel: + case R.string.menu_record: + startService(item.getIntent()); + return true; + default: + return super.onOptionsItemSelected(item); + } + } } diff --git a/src/org/tvheadend/tvhguide/ProgrammeListActivity.java b/src/org/tvheadend/tvhguide/ProgrammeListActivity.java index 73a1457..6fd76fb 100644 --- a/src/org/tvheadend/tvhguide/ProgrammeListActivity.java +++ b/src/org/tvheadend/tvhguide/ProgrammeListActivity.java @@ -18,15 +18,27 @@ */ package org.tvheadend.tvhguide; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.tvheadend.tvhguide.R.string; +import org.tvheadend.tvhguide.htsp.HTSListener; +import org.tvheadend.tvhguide.htsp.HTSService; +import org.tvheadend.tvhguide.intent.SearchEPGIntent; +import org.tvheadend.tvhguide.intent.SearchIMDbIntent; +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.Programme; +import org.tvheadend.tvhguide.model.Recording; +import org.tvheadend.tvhguide.model.SeriesInfo; + import android.app.Activity; import android.app.ListActivity; import android.content.Intent; -import android.content.SharedPreferences; +import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.util.SparseArray; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; @@ -35,285 +47,256 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; -import android.view.Window; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; -import android.widget.ImageView; import android.widget.ListView; -import android.widget.TextView; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; - -import org.tvheadend.tvhguide.R; -import org.tvheadend.tvhguide.R.string; -import org.tvheadend.tvhguide.htsp.HTSListener; -import org.tvheadend.tvhguide.htsp.HTSService; -import org.tvheadend.tvhguide.intent.SearchEPGIntent; -import org.tvheadend.tvhguide.intent.SearchIMDbIntent; -import org.tvheadend.tvhguide.model.Channel; -import org.tvheadend.tvhguide.model.Programme; -import org.tvheadend.tvhguide.model.Recording; -import org.tvheadend.tvhguide.model.SeriesInfo; /** - * + * * @author john-tornblom */ public class ProgrammeListActivity extends ListActivity implements HTSListener { - private ProgrammeListAdapter prAdapter; - private Channel channel; - private SparseArray contentTypes; - - @Override - public void onCreate(Bundle icicle) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - Boolean theme = prefs.getBoolean("lightThemePref", false); - setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme); - - super.onCreate(icicle); - - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - channel = app.getChannel(getIntent().getLongExtra("channelId", 0)); - - if (channel == null) { - finish(); - return; - } - - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - - Button btn = new Button(this); - btn.setText(R.string.pr_get_more); - btn.setOnClickListener(new OnClickListener() { - - public void onClick(View view) { - Programme p = null; - - Iterator it = channel.epg.iterator(); - long nextId = 0; - - while (it.hasNext()) { - p = it.next(); - if (p.id != nextId && nextId != 0) { - break; - } - nextId = p.nextId; - } - if(p == null) - return; - - if (nextId == 0) { - nextId = p.nextId; - } - if (nextId == 0) { - nextId = p.id; - } - Intent intent = new Intent(ProgrammeListActivity.this, HTSService.class); - intent.setAction(HTSService.ACTION_GET_EVENTS); - intent.putExtra("eventId", nextId); - intent.putExtra("channelId", channel.id); - intent.putExtra("count", 10); - startService(intent); - } - }); - - getListView().addFooterView(btn); - - List prList = new ArrayList(); - prList.addAll(channel.epg); - prAdapter = new ProgrammeListAdapter(this, prList); - prAdapter.sort(); - setListAdapter(prAdapter); - - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.programme_list_title); - TextView t = (TextView) findViewById(R.id.ct_title); - t.setText(channel.name); - - if (channel.iconBitmap != null) { - ImageView iv = (ImageView) findViewById(R.id.ct_logo); - iv.setImageBitmap(channel.iconBitmap); - } - - View v = findViewById(R.id.ct_btn); - v.setOnClickListener(new android.view.View.OnClickListener() { - - public void onClick(View arg0) { - Intent intent = new Intent(ProgrammeListActivity.this, PlaybackActivity.class); - intent.putExtra("channelId", channel.id); - startActivity(intent); - } - }); - - registerForContextMenu(getListView()); - contentTypes = TVHGuideApplication.getContentTypes(this); - } - - @Override - protected void onResume() { - super.onResume(); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.addListener(this); - } - - @Override - protected void onPause() { - super.onPause(); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.removeListener(this); - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - Programme p = (Programme) prAdapter.getItem(position); - - Intent intent = new Intent(this, ProgrammeActivity.class); - intent.putExtra("eventId", p.id); - intent.putExtra("channelId", p.channel.id); - startActivity(intent); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.string.menu_record: - case R.string.menu_record_cancel: - case R.string.menu_record_remove: { - startService(item.getIntent()); - return true; - } - default: { - return super.onContextItemSelected(item); - } - } - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Programme p = prAdapter.getItem(info.position); - - menu.setHeaderTitle(p.title); - - Intent intent = new Intent(this, HTSService.class); - - MenuItem item = null; - - if (p.recording == null) { - intent.setAction(HTSService.ACTION_DVR_ADD); - intent.putExtra("eventId", p.id); - intent.putExtra("channelId", p.channel.id); - item = menu.add(ContextMenu.NONE, R.string.menu_record, ContextMenu.NONE, R.string.menu_record); - } else if (p.isRecording() || p.isScheduled()) { - intent.setAction(HTSService.ACTION_DVR_CANCEL); - intent.putExtra("id", p.recording.id); - item = menu.add(ContextMenu.NONE, R.string.menu_record_cancel, ContextMenu.NONE, R.string.menu_record_cancel); - } else { - intent.setAction(HTSService.ACTION_DVR_DELETE); - intent.putExtra("id", p.recording.id); - item = menu.add(ContextMenu.NONE, R.string.menu_record_remove, ContextMenu.NONE, R.string.menu_record_remove); - } - - item.setIntent(intent); - - item = menu.add(ContextMenu.NONE, R.string.search_hint, ContextMenu.NONE, R.string.search_hint); - item.setIntent(new SearchEPGIntent(this, p.title)); - - item = menu.add(ContextMenu.NONE, ContextMenu.NONE, ContextMenu.NONE, "IMDb"); - item.setIntent(new SearchIMDbIntent(this, p.title)); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuItem item = null; - Intent intent = null; - - item = menu.add(Menu.NONE, android.R.string.search_go, Menu.NONE, android.R.string.search_go); - item.setIcon(android.R.drawable.ic_menu_search); - - intent = new Intent(ProgrammeListActivity.this, PlaybackActivity.class); - intent.putExtra("channelId", channel.id); - - item = menu.add(Menu.NONE, R.string.ch_play, Menu.NONE, R.string.ch_play); - item.setIcon(android.R.drawable.ic_menu_view); - item.setIntent(intent); - - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.string.search_go: { - onSearchRequested(); - return true; - } - default: { - return super.onOptionsItemSelected(item); - } - } - } - - @Override - public boolean onSearchRequested() { - Bundle bundle = new Bundle(); - bundle.putLong("channelId", channel.id); - startSearch(null, false, bundle, false); - return true; - } - - public void onMessage(String action, final Object obj) { - if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_ADD)) { - runOnUiThread(new Runnable() { - - public void run() { - Programme p = (Programme) obj; - if (channel != null && p.channel.id == channel.id) { - prAdapter.add(p); - prAdapter.notifyDataSetChanged(); - prAdapter.sort(); - } - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_DELETE)) { - runOnUiThread(new Runnable() { - - public void run() { - Programme p = (Programme) obj; - prAdapter.remove(p); - prAdapter.notifyDataSetChanged(); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_UPDATE)) { - runOnUiThread(new Runnable() { - - public void run() { - Programme p = (Programme) obj; - prAdapter.updateView(getListView(), p); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_DVR_UPDATE)) { - runOnUiThread(new Runnable() { - - public void run() { - Recording rec = (Recording) obj; - for (Programme p : prAdapter.list) { - if (rec == p.recording) { - prAdapter.updateView(getListView(), p); - return; - } - } - } - }); - } - } + private ProgrammeListAdapter prAdapter; + private Channel channel; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + channel = app.getChannel(getIntent().getLongExtra("channelId", 0)); + + if (channel == null) { + finish(); + return; + } + + Button btn = new Button(this); + btn.setText(R.string.pr_get_more); + btn.setOnClickListener(new OnClickListener() { + + public void onClick(View view) { + Programme p = null; + + Iterator it = channel.epg.iterator(); + long nextId = 0; + + while (it.hasNext()) { + p = it.next(); + if (p.id != nextId && nextId != 0) { + break; + } + nextId = p.nextId; + } + if (p == null) + return; + + if (nextId == 0) { + nextId = p.nextId; + } + if (nextId == 0) { + nextId = p.id; + } + Intent intent = new Intent(ProgrammeListActivity.this, + HTSService.class); + intent.setAction(HTSService.ACTION_GET_EVENTS); + intent.putExtra("eventId", nextId); + intent.putExtra("channelId", channel.id); + intent.putExtra("count", 10); + startService(intent); + } + }); + + getListView().addFooterView(btn); + + List prList = new ArrayList(); + prList.addAll(channel.epg); + prAdapter = new ProgrammeListAdapter(this, prList); + prAdapter.sort(); + setListAdapter(prAdapter); + + registerForContextMenu(getListView()); + } + + @Override + protected void onResume() { + super.onResume(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.addListener(this); + } + + @Override + protected void onPause() { + super.onPause(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.removeListener(this); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Programme p = (Programme) prAdapter.getItem(position); + + Intent intent = new Intent(this, ProgrammeActivity.class); + intent.putExtra("eventId", p.id); + intent.putExtra("channelId", p.channel.id); + startActivity(intent); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.string.menu_record: + case R.string.menu_record_cancel: + case R.string.menu_record_remove: { + startService(item.getIntent()); + return true; + } + default: { + return super.onContextItemSelected(item); + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + Programme p = prAdapter.getItem(info.position); + + menu.setHeaderTitle(p.title); + + Intent intent = new Intent(this, HTSService.class); + + MenuItem item = null; + + if (p.recording == null) { + intent.setAction(HTSService.ACTION_DVR_ADD); + intent.putExtra("eventId", p.id); + intent.putExtra("channelId", p.channel.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record, + ContextMenu.NONE, R.string.menu_record); + } else if (p.isRecording() || p.isScheduled()) { + intent.setAction(HTSService.ACTION_DVR_CANCEL); + intent.putExtra("id", p.recording.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record_cancel, + ContextMenu.NONE, R.string.menu_record_cancel); + } else { + intent.setAction(HTSService.ACTION_DVR_DELETE); + intent.putExtra("id", p.recording.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record_remove, + ContextMenu.NONE, R.string.menu_record_remove); + } + + item.setIntent(intent); + + item = menu.add(ContextMenu.NONE, R.string.search_hint, + ContextMenu.NONE, R.string.search_hint); + item.setIntent(new SearchEPGIntent(this, p.title)); + + item = menu.add(ContextMenu.NONE, ContextMenu.NONE, ContextMenu.NONE, + "IMDb"); + item.setIntent(new SearchIMDbIntent(this, p.title)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuItem item = null; + Intent intent = null; + + item = menu.add(Menu.NONE, android.R.string.search_go, Menu.NONE, + android.R.string.search_go); + item.setIcon(android.R.drawable.ic_menu_search); + + intent = new Intent(ProgrammeListActivity.this, PlaybackActivity.class); + intent.putExtra("channelId", channel.id); + + item = menu.add(Menu.NONE, R.string.ch_play, Menu.NONE, + R.string.ch_play); + item.setIcon(android.R.drawable.ic_menu_view); + item.setIntent(intent); + + if (getActionBar() != null && channel != null) { + getActionBar().setTitle(channel.name); + + if (channel.iconBitmap != null) { + getActionBar().setIcon( + new BitmapDrawable(getResources(), channel.iconBitmap)); + } + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.string.search_go: { + onSearchRequested(); + return true; + } + default: { + return super.onOptionsItemSelected(item); + } + } + } + + @Override + public boolean onSearchRequested() { + Bundle bundle = new Bundle(); + bundle.putLong("channelId", channel.id); + startSearch(null, false, bundle, false); + return true; + } + + public void onMessage(String action, final Object obj) { + if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_ADD)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + if (channel != null && p.channel.id == channel.id) { + prAdapter.add(p); + prAdapter.notifyDataSetChanged(); + prAdapter.sort(); + } + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_DELETE)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + prAdapter.remove(p); + prAdapter.notifyDataSetChanged(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + prAdapter.updateView(getListView(), p); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_DVR_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Recording rec = (Recording) obj; + for (Programme p : prAdapter.list) { + if (rec == p.recording) { + prAdapter.updateView(getListView(), p); + return; + } + } + } + }); + } + } public String buildSeriesInfoString(SeriesInfo info) { if (info.onScreen != null && info.onScreen.length() > 0) @@ -323,175 +306,99 @@ public String buildSeriesInfoString(SeriesInfo info) { String season = this.getResources().getString(string.pr_season); String episode = this.getResources().getString(string.pr_episode); String part = this.getResources().getString(string.pr_part); - - if(info.onScreen.length() > 0) { + + if (info.onScreen.length() > 0) { return info.onScreen; } - + if (info.seasonNumber > 0) { if (s.length() > 0) s += ", "; - s += String.format("%s %02d", season.toLowerCase(), info.seasonNumber); + s += String.format("%s %02d", + season.toLowerCase(Locale.getDefault()), info.seasonNumber); } if (info.episodeNumber > 0) { if (s.length() > 0) s += ", "; - s += String.format("%s %02d", episode.toLowerCase(), info.episodeNumber); + s += String.format("%s %02d", + episode.toLowerCase(Locale.getDefault()), + info.episodeNumber); } if (info.partNumber > 0) { if (s.length() > 0) s += ", "; - s += String.format("%s %d", part.toLowerCase(), info.partNumber); + s += String.format("%s %d", part.toLowerCase(Locale.getDefault()), + info.partNumber); } - if(s.length() > 0) { - s = s.substring(0,1).toUpperCase() + s.substring(1); + if (s.length() > 0) { + s = s.substring(0, 1).toUpperCase(Locale.getDefault()) + + s.substring(1); } - + return s; } - - private class ViewWarpper { - - TextView title; - TextView time; - TextView seriesInfo; - TextView date; - TextView description; - ImageView state; - - public ViewWarpper(View base) { - title = (TextView) base.findViewById(R.id.pr_title); - description = (TextView) base.findViewById(R.id.pr_desc); - seriesInfo = (TextView) base.findViewById(R.id.pr_series_info); - - time = (TextView) base.findViewById(R.id.pr_time); - date = (TextView) base.findViewById(R.id.pr_date); - - state = (ImageView) base.findViewById(R.id.pr_state); - } - - public void repaint(Programme p) { - title.setText(p.title); - - if (p.recording == null) { - state.setImageDrawable(null); - } else if (p.recording.error != null) { - state.setImageResource(R.drawable.ic_error_small); - } else if ("completed".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_success_small); - } else if ("invalid".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_error_small); - } else if ("missed".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_error_small); - } else if ("recording".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_rec_small); - } else if ("scheduled".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_schedule_small); - } else { - state.setImageDrawable(null); - } - - title.invalidate(); - - String s = buildSeriesInfoString(p.seriesInfo); - if(s.length() == 0) { - s = contentTypes.get(p.contentType); - } - - seriesInfo.setText(s); - seriesInfo.invalidate(); - - if (p.description.length() > 0) { - description.setText(p.description); - description.setVisibility(TextView.VISIBLE); - } else { - description.setText(""); - description.setVisibility(TextView.GONE); - } - description.invalidate(); - - if (DateUtils.isToday(p.start.getTime())) { - date.setText(getString(R.string.today)); - } else if(p.start.getTime() < System.currentTimeMillis() + 1000*60*60*24*2 && - p.start.getTime() > System.currentTimeMillis() - 1000*60*60*24*2) { - date.setText(DateUtils.getRelativeTimeSpanString(p.start.getTime(), - System.currentTimeMillis(), DateUtils.DAY_IN_MILLIS)); - } else if(p.start.getTime() < System.currentTimeMillis() + 1000*60*60*24*6 && - p.start.getTime() > System.currentTimeMillis() - 1000*60*60*24*2) { - date.setText(new SimpleDateFormat("EEEE").format(p.start.getTime())); - } else { - date.setText(DateFormat.getDateFormat(date.getContext()).format(p.start)); - } - - date.invalidate(); - - time.setText( - DateFormat.getTimeFormat(time.getContext()).format(p.start) - + " - " - + DateFormat.getTimeFormat(time.getContext()).format(p.stop)); - time.invalidate(); - } - } - - class ProgrammeListAdapter extends ArrayAdapter { - - Activity context; - List list; - - ProgrammeListAdapter(Activity context, List list) { - super(context, R.layout.programme_list_widget, list); - this.context = context; - this.list = list; - } - - public void sort() { - sort(new Comparator() { - - public int compare(Programme x, Programme y) { - return x.compareTo(y); - } - }); - } - - public void updateView(ListView listView, Programme programme) { - for (int i = 0; i < listView.getChildCount(); i++) { - View view = listView.getChildAt(i); - int pos = listView.getPositionForView(view); - Programme pr = (Programme) listView.getItemAtPosition(pos); - - if (view.getTag() == null || pr == null) { - continue; - } - - if (programme.id != pr.id) { - continue; - } - - ViewWarpper wrapper = (ViewWarpper) view.getTag(); - wrapper.repaint(programme); - } - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View row = convertView; - ViewWarpper wrapper = null; - - if (row == null) { - LayoutInflater inflater = context.getLayoutInflater(); - row = inflater.inflate(R.layout.programme_list_widget, null, false); - - wrapper = new ViewWarpper(row); - row.setTag(wrapper); - - } else { - wrapper = (ViewWarpper) row.getTag(); - } - - Programme p = getItem(position); - wrapper.repaint(p); - return row; - } - } + + class ProgrammeListAdapter extends ArrayAdapter { + + Activity context; + List list; + + ProgrammeListAdapter(Activity context, List list) { + super(context, R.layout.programme_list_widget, list); + this.context = context; + this.list = list; + } + + public void sort() { + sort(new Comparator() { + + public int compare(Programme x, Programme y) { + return x.compareTo(y); + } + }); + } + + public void updateView(ListView listView, Programme programme) { + for (int i = 0; i < listView.getChildCount(); i++) { + View view = listView.getChildAt(i); + int pos = listView.getPositionForView(view); + Programme pr = (Programme) listView.getItemAtPosition(pos); + + if (view.getTag() == null || pr == null) { + continue; + } + + if (programme.id != pr.id) { + continue; + } + + ProgrammeListViewWrapper wrapper = (ProgrammeListViewWrapper) view + .getTag(); + wrapper.repaint(programme); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + ProgrammeListViewWrapper wrapper = null; + + if (row == null) { + LayoutInflater inflater = context.getLayoutInflater(); + row = inflater.inflate(R.layout.programme_list_widget, null, + false); + + wrapper = new ProgrammeListViewWrapper(row); + row.setTag(wrapper); + + } else { + wrapper = (ProgrammeListViewWrapper) row.getTag(); + } + + Programme p = getItem(position); + wrapper.repaint(p); + return row; + } + } } diff --git a/src/org/tvheadend/tvhguide/ProgrammeListViewWrapper.java b/src/org/tvheadend/tvhguide/ProgrammeListViewWrapper.java new file mode 100644 index 0000000..d993dd3 --- /dev/null +++ b/src/org/tvheadend/tvhguide/ProgrammeListViewWrapper.java @@ -0,0 +1,146 @@ +package org.tvheadend.tvhguide; + +import java.text.SimpleDateFormat; +import java.util.Locale; + +import org.tvheadend.tvhguide.R.string; +import org.tvheadend.tvhguide.model.Programme; +import org.tvheadend.tvhguide.model.SeriesInfo; + +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.SparseArray; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +class ProgrammeListViewWrapper { + + TextView title; + TextView time; + TextView seriesInfo; + TextView date; + TextView description; + ImageView state; + private final View base; + private SparseArray contentTypes; + + public ProgrammeListViewWrapper(View base) { + this.base = base; + title = (TextView) base.findViewById(R.id.pr_title); + description = (TextView) base.findViewById(R.id.pr_desc); + seriesInfo = (TextView) base.findViewById(R.id.pr_series_info); + + time = (TextView) base.findViewById(R.id.pr_time); + date = (TextView) base.findViewById(R.id.pr_date); + + state = (ImageView) base.findViewById(R.id.pr_state); + + contentTypes = TVHGuideApplication.getContentTypes(base.getResources()); + + } + + public void repaint(Programme p) { + title.setText(p.title); + + RecordUtil.applyRecording(p.recording, state); + + title.invalidate(); + + if (seriesInfo != null) { + String s = buildSeriesInfoString(base, p.seriesInfo); + if (s.length() == 0) { + s = contentTypes.get(p.contentType); + } + + seriesInfo.setText(s); + seriesInfo.invalidate(); + } + + if (description != null) { + if (p.description.length() > 0) { + description.setText(p.description); + description.setVisibility(TextView.VISIBLE); + } else { + description.setText(""); + description.setVisibility(TextView.GONE); + } + description.invalidate(); + } + + if (date != null) { + if (DateUtils.isToday(p.start.getTime())) { + date.setText(base.getResources().getString(R.string.today)); + } else if (p.start.getTime() < System.currentTimeMillis() + 1000 + * 60 * 60 * 24 * 2 + && p.start.getTime() > System.currentTimeMillis() - 1000 + * 60 * 60 * 24 * 2) { + date.setText(DateUtils.getRelativeTimeSpanString( + p.start.getTime(), System.currentTimeMillis(), + DateUtils.DAY_IN_MILLIS)); + } else if (p.start.getTime() < System.currentTimeMillis() + 1000 + * 60 * 60 * 24 * 6 + && p.start.getTime() > System.currentTimeMillis() - 1000 + * 60 * 60 * 24 * 2) { + date.setText(new SimpleDateFormat("EEEE", Locale.getDefault()) + .format(p.start.getTime())); + } else { + date.setText(DateFormat.getDateFormat(date.getContext()) + .format(p.start)); + } + + date.invalidate(); + } + + if (time != null) { + time.setText(DateFormat.getTimeFormat(time.getContext()).format( + p.start) + + " - " + + DateFormat.getTimeFormat(time.getContext()) + .format(p.stop)); + time.invalidate(); + } + } + + public String buildSeriesInfoString(View context, SeriesInfo info) { + if (info.onScreen != null && info.onScreen.length() > 0) + return info.onScreen; + + String s = ""; + String season = context.getResources().getString(string.pr_season); + String episode = context.getResources().getString(string.pr_episode); + String part = context.getResources().getString(string.pr_part); + + if (info.onScreen.length() > 0) { + return info.onScreen; + } + + if (info.seasonNumber > 0) { + if (s.length() > 0) + s += ", "; + s += String.format("%s %02d", + season.toLowerCase(Locale.getDefault()), info.seasonNumber); + } + if (info.episodeNumber > 0) { + if (s.length() > 0) + s += ", "; + s += String.format("%s %02d", + episode.toLowerCase(Locale.getDefault()), + info.episodeNumber); + } + if (info.partNumber > 0) { + if (s.length() > 0) + s += ", "; + s += String.format("%s %d", part.toLowerCase(Locale.getDefault()), + info.partNumber); + } + + if (s.length() > 0) { + s = s.substring(0, 1).toUpperCase(Locale.getDefault()) + + s.substring(1); + } + + return s; + } + +} \ No newline at end of file diff --git a/src/org/tvheadend/tvhguide/RecordUtil.java b/src/org/tvheadend/tvhguide/RecordUtil.java new file mode 100644 index 0000000..a06ed2c --- /dev/null +++ b/src/org/tvheadend/tvhguide/RecordUtil.java @@ -0,0 +1,27 @@ +package org.tvheadend.tvhguide; + +import org.tvheadend.tvhguide.model.Recording; + +import android.widget.ImageView; + +public class RecordUtil { + public static void applyRecording(Recording rec, ImageView state) { + if (rec == null) { + state.setImageDrawable(null); + } else if (rec.error != null) { + state.setImageResource(R.drawable.ic_error_small); + } else if ("completed".equals(rec.state)) { + state.setImageResource(R.drawable.ic_success_small); + } else if ("invalid".equals(rec.state)) { + state.setImageResource(R.drawable.ic_error_small); + } else if ("missed".equals(rec.state)) { + state.setImageResource(R.drawable.ic_error_small); + } else if ("recording".equals(rec.state)) { + state.setImageResource(R.drawable.ic_rec_small); + } else if ("scheduled".equals(rec.state)) { + state.setImageResource(R.drawable.ic_schedule_small); + } else { + state.setImageDrawable(null); + } + } +} diff --git a/src/org/tvheadend/tvhguide/RecordingActivity.java b/src/org/tvheadend/tvhguide/RecordingActivity.java index a3acaac..c95668d 100644 --- a/src/org/tvheadend/tvhguide/RecordingActivity.java +++ b/src/org/tvheadend/tvhguide/RecordingActivity.java @@ -18,6 +18,11 @@ */ package org.tvheadend.tvhguide; +import org.tvheadend.tvhguide.htsp.HTSService; +import org.tvheadend.tvhguide.intent.SearchEPGIntent; +import org.tvheadend.tvhguide.intent.SearchIMDbIntent; +import org.tvheadend.tvhguide.model.Recording; + import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; @@ -32,149 +37,151 @@ import android.widget.ImageView; import android.widget.TextView; -import org.tvheadend.tvhguide.R; -import org.tvheadend.tvhguide.htsp.HTSService; -import org.tvheadend.tvhguide.intent.SearchEPGIntent; -import org.tvheadend.tvhguide.intent.SearchIMDbIntent; -import org.tvheadend.tvhguide.model.Recording; - /** - * + * * @author john-tornblom */ public class RecordingActivity extends Activity { - Recording rec; - - @Override - public void onCreate(Bundle savedInstanceState) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - Boolean theme = prefs.getBoolean("lightThemePref", false); - setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme); - - super.onCreate(savedInstanceState); - - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - rec = app.getRecording(getIntent().getLongExtra("id", 0)); - if (rec == null) { - finish(); - return; - } - - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - - setContentView(R.layout.recording_layout); - - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.recording_title); - TextView t = (TextView) findViewById(R.id.ct_title); - t.setText(rec.channel.name); - - if (rec.channel.iconBitmap != null) { - ImageView iv = (ImageView) findViewById(R.id.ct_logo); - iv.setImageBitmap(rec.channel.iconBitmap); - } - - TextView text = (TextView) findViewById(R.id.rec_name); - text.setText(rec.title); - - text = (TextView) findViewById(R.id.rec_summary); - text.setText(rec.summary); - if(rec.summary.length() == 0) - text.setVisibility(TextView.GONE); - - text = (TextView) findViewById(R.id.rec_desc); - text.setText(rec.description); - - text = (TextView) findViewById(R.id.rec_time); - text.setText( - DateFormat.getLongDateFormat(this).format(rec.start) - + " " - + DateFormat.getTimeFormat(this).format(rec.start) - + " - " - + DateFormat.getTimeFormat(this).format(rec.stop)); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuItem item = null; - - if (rec.title != null) { - item = menu.add(Menu.NONE, android.R.string.search_go, Menu.NONE, android.R.string.search_go); - item.setIntent(new SearchEPGIntent(this, rec.title)); - item.setIcon(android.R.drawable.ic_menu_search); - - item = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, "IMDb"); - item.setIntent(new SearchIMDbIntent(this, rec.title)); - item.setIcon(android.R.drawable.ic_menu_info_details); - } - - Intent intent = new Intent(this, HTSService.class); - - if (rec.isRecording() || rec.isScheduled()) { - intent.setAction(HTSService.ACTION_DVR_CANCEL); - intent.putExtra("id", rec.id); - item = menu.add(Menu.NONE, R.string.menu_record_cancel, Menu.NONE, R.string.menu_record_cancel); - item.setIntent(intent); - item.setIcon(android.R.drawable.ic_menu_close_clear_cancel); - item.setIntent(intent); - } else { - intent.setAction(HTSService.ACTION_DVR_DELETE); - intent.putExtra("id", rec.id); - item = menu.add(Menu.NONE, R.string.menu_record_remove, Menu.NONE, R.string.menu_record_remove); - item.setIntent(intent); - item.setIcon(android.R.drawable.ic_menu_delete); - item.setIntent(intent); - - intent = new Intent(this, ExternalPlaybackActivity.class); - intent.putExtra("dvrId", rec.id); - item = menu.add(Menu.NONE, R.string.ch_play, Menu.NONE, R.string.ch_play); - item.setIntent(intent); - item.setIcon(android.R.drawable.ic_menu_view); - } - - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - boolean rebuild = false; - if (rec.isRecording() || rec.isScheduled()) { - rebuild = menu.findItem(R.string.menu_record_cancel) == null; - } else { - rebuild = menu.findItem(R.string.menu_record_remove) == null; - } - - if (rebuild) { - menu.clear(); - return onCreateOptionsMenu(menu); - } - - return true; - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.string.menu_record_remove: { - new AlertDialog.Builder(this) - .setTitle(R.string.menu_record_remove) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - - public void onClick(DialogInterface dialog, int which) { - startService(item.getIntent()); - } - }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { - - public void onClick(DialogInterface dialog, int which) { - //NOP - } - }).show(); - } - case R.string.menu_record_cancel: - startService(item.getIntent()); - return true; - default: - return super.onOptionsItemSelected(item); - } - } + Recording rec; + + @Override + public void onCreate(Bundle savedInstanceState) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + Boolean theme = prefs.getBoolean("lightThemePref", false); + setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme); + + super.onCreate(savedInstanceState); + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + rec = app.getRecording(getIntent().getLongExtra("id", 0)); + if (rec == null) { + finish(); + return; + } + + requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); + + setContentView(R.layout.recording_layout); + + getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, + R.layout.recording_title); + TextView t = (TextView) findViewById(R.id.ct_title); + t.setText(rec.channel.name); + + if (rec.channel.iconBitmap != null) { + ImageView iv = (ImageView) findViewById(R.id.ct_logo); + iv.setImageBitmap(rec.channel.iconBitmap); + } + + TextView text = (TextView) findViewById(R.id.rec_name); + text.setText(rec.title); + + text = (TextView) findViewById(R.id.rec_summary); + text.setText(rec.summary); + if (rec.summary.length() == 0) + text.setVisibility(TextView.GONE); + + text = (TextView) findViewById(R.id.rec_desc); + text.setText(rec.description); + + text = (TextView) findViewById(R.id.rec_time); + text.setText(DateFormat.getLongDateFormat(this).format(rec.start) + + " " + DateFormat.getTimeFormat(this).format(rec.start) + + " - " + DateFormat.getTimeFormat(this).format(rec.stop)); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuItem item = null; + + if (rec.title != null) { + item = menu.add(Menu.NONE, android.R.string.search_go, Menu.NONE, + android.R.string.search_go); + item.setIntent(new SearchEPGIntent(this, rec.title)); + item.setIcon(android.R.drawable.ic_menu_search); + + item = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, "IMDb"); + item.setIntent(new SearchIMDbIntent(this, rec.title)); + item.setIcon(android.R.drawable.ic_menu_info_details); + } + + Intent intent = new Intent(this, HTSService.class); + + if (rec.isRecording() || rec.isScheduled()) { + intent.setAction(HTSService.ACTION_DVR_CANCEL); + intent.putExtra("id", rec.id); + item = menu.add(Menu.NONE, R.string.menu_record_cancel, Menu.NONE, + R.string.menu_record_cancel); + item.setIntent(intent); + item.setIcon(android.R.drawable.ic_menu_close_clear_cancel); + item.setIntent(intent); + } else { + intent.setAction(HTSService.ACTION_DVR_DELETE); + intent.putExtra("id", rec.id); + item = menu.add(Menu.NONE, R.string.menu_record_remove, Menu.NONE, + R.string.menu_record_remove); + item.setIntent(intent); + item.setIcon(android.R.drawable.ic_menu_delete); + item.setIntent(intent); + + intent = new Intent(this, ExternalPlaybackActivity.class); + intent.putExtra("dvrId", rec.id); + item = menu.add(Menu.NONE, R.string.ch_play, Menu.NONE, + R.string.ch_play); + item.setIntent(intent); + item.setIcon(android.R.drawable.ic_menu_view); + } + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + boolean rebuild = false; + if (rec.isRecording() || rec.isScheduled()) { + rebuild = menu.findItem(R.string.menu_record_cancel) == null; + } else { + rebuild = menu.findItem(R.string.menu_record_remove) == null; + } + + if (rebuild) { + menu.clear(); + return onCreateOptionsMenu(menu); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.string.menu_record_remove: { + new AlertDialog.Builder(this) + .setTitle(R.string.menu_record_remove) + .setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, + int which) { + startService(item.getIntent()); + } + }) + .setNegativeButton(android.R.string.no, + new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, + int which) { + // NOP + } + }).show(); + } + case R.string.menu_record_cancel: + startService(item.getIntent()); + return true; + default: + return super.onOptionsItemSelected(item); + } + } } diff --git a/src/org/tvheadend/tvhguide/RecordingListActivity.java b/src/org/tvheadend/tvhguide/RecordingListActivity.java index ba30d55..b3396bf 100644 --- a/src/org/tvheadend/tvhguide/RecordingListActivity.java +++ b/src/org/tvheadend/tvhguide/RecordingListActivity.java @@ -18,6 +18,19 @@ */ package org.tvheadend.tvhguide; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; + +import org.tvheadend.tvhguide.htsp.HTSListener; +import org.tvheadend.tvhguide.htsp.HTSService; +import org.tvheadend.tvhguide.intent.SearchEPGIntent; +import org.tvheadend.tvhguide.intent.SearchIMDbIntent; +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.Recording; + import android.app.Activity; import android.app.AlertDialog; import android.app.ListActivity; @@ -41,340 +54,348 @@ import android.widget.ListView; import android.widget.TextView; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import org.tvheadend.tvhguide.R; -import org.tvheadend.tvhguide.htsp.HTSListener; -import org.tvheadend.tvhguide.htsp.HTSService; -import org.tvheadend.tvhguide.intent.SearchEPGIntent; -import org.tvheadend.tvhguide.intent.SearchIMDbIntent; -import org.tvheadend.tvhguide.model.Channel; -import org.tvheadend.tvhguide.model.Recording; - /** - * + * * @author john-tornblom */ public class RecordingListActivity extends ListActivity implements HTSListener { - private RecordingListAdapter recAdapter; - - @Override - public void onCreate(Bundle icicle) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - Boolean theme = prefs.getBoolean("lightThemePref", false); - setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme); - - super.onCreate(icicle); - - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - - List recList = new ArrayList(); - recList.addAll(app.getRecordings()); - recAdapter = new RecordingListAdapter(this, recList); - recAdapter.sort(); - setListAdapter(recAdapter); - registerForContextMenu(getListView()); - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.recording_list_title); - TextView t = (TextView) findViewById(R.id.ct_title); - - t.setText(R.string.menu_recordings); - } - - @Override - protected void onResume() { - super.onResume(); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.addListener(this); - } - - @Override - protected void onPause() { - super.onPause(); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.removeListener(this); - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - Recording rec = (Recording) recAdapter.getItem(position); - - Intent intent = new Intent(this, RecordingActivity.class); - intent.putExtra("id", rec.id); - startActivity(intent); - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - - MenuItem item = null; - Intent intent = null; - - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Recording rec = recAdapter.getItem(info.position); - - menu.setHeaderTitle(rec.title); - - intent = new Intent(RecordingListActivity.this, HTSService.class); - intent.putExtra("id", rec.id); - - if (rec.isRecording() || rec.isScheduled()) { - intent.setAction(HTSService.ACTION_DVR_CANCEL); - item = menu.add(ContextMenu.NONE, R.string.menu_record_cancel, ContextMenu.NONE, R.string.menu_record_cancel); - item.setIntent(intent); - } else { - intent.setAction(HTSService.ACTION_DVR_DELETE); - item = menu.add(ContextMenu.NONE, R.string.menu_record_remove, ContextMenu.NONE, R.string.menu_record_remove); - item.setIntent(intent); - - item = menu.add(ContextMenu.NONE, R.string.ch_play, ContextMenu.NONE, R.string.ch_play); - intent = new Intent(this, ExternalPlaybackActivity.class); - intent.putExtra("dvrId", rec.id); - item.setIntent(intent); - item.setIcon(android.R.drawable.ic_menu_view); - } - - item = menu.add(ContextMenu.NONE, R.string.search_hint, ContextMenu.NONE, R.string.search_hint); - item.setIntent(new SearchEPGIntent(this, rec.title)); - item.setIcon(android.R.drawable.ic_menu_search); - - item = menu.add(ContextMenu.NONE, ContextMenu.NONE, ContextMenu.NONE, "IMDb"); - item.setIntent(new SearchIMDbIntent(this, rec.title)); - item.setIcon(android.R.drawable.ic_menu_info_details); - } - - @Override - public boolean onContextItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.string.menu_record: - case R.string.menu_record_cancel: { - startService(item.getIntent()); - return true; - } - case R.string.menu_record_remove: { - - new AlertDialog.Builder(this) - .setTitle(R.string.menu_record_remove) - .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - startService(item.getIntent()); - } - }) - .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - //NOP - } - }) - .show(); - - return true; - } - default: { - return super.onContextItemSelected(item); - } - } - } - - public void onMessage(String action, final Object obj) { - if (action.equals(TVHGuideApplication.ACTION_LOADING) && !(Boolean) obj) { - - runOnUiThread(new Runnable() { - - public void run() { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - recAdapter.list.clear(); - recAdapter.list.addAll(app.getRecordings()); - recAdapter.notifyDataSetChanged(); - recAdapter.sort(); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_DVR_ADD)) { - runOnUiThread(new Runnable() { - - public void run() { - recAdapter.add((Recording) obj); - recAdapter.notifyDataSetChanged(); - recAdapter.sort(); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_DVR_DELETE)) { - runOnUiThread(new Runnable() { - - public void run() { - recAdapter.remove((Recording) obj); - recAdapter.notifyDataSetChanged(); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_DVR_UPDATE)) { - runOnUiThread(new Runnable() { - - public void run() { - Recording rec = (Recording) obj; - recAdapter.updateView(getListView(), rec); - } - }); - } - } - - private class ViewWarpper { - - TextView title; - TextView channel; - TextView time; - TextView date; - TextView message; - TextView desc; - ImageView icon; - ImageView state; - - public ViewWarpper(View base) { - title = (TextView) base.findViewById(R.id.rec_title); - channel = (TextView) base.findViewById(R.id.rec_channel); - - time = (TextView) base.findViewById(R.id.rec_time); - date = (TextView) base.findViewById(R.id.rec_date); - message = (TextView) base.findViewById(R.id.rec_message); - desc = (TextView) base.findViewById(R.id.rec_desc); - icon = (ImageView) base.findViewById(R.id.rec_icon); - state = (ImageView) base.findViewById(R.id.rec_state); - } - - public void repaint(Recording rec) { - Channel ch = rec.channel; - - title.setText(rec.title); - title.invalidate(); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(icon.getContext()); - Boolean showIcons = prefs.getBoolean("showIconPref", false); - icon.setVisibility(showIcons ? ImageView.VISIBLE : ImageView.GONE); - icon.setImageBitmap(ch.iconBitmap); - - channel.setText(ch.name); - channel.invalidate(); - - if (DateUtils.isToday(rec.start.getTime())) { - date.setText(getString(R.string.today)); - } else if(rec.start.getTime() < System.currentTimeMillis() + 1000*60*60*24*2 && - rec.start.getTime() > System.currentTimeMillis() - 1000*60*60*24*2) { - date.setText(DateUtils.getRelativeTimeSpanString(rec.start.getTime(), - System.currentTimeMillis(), DateUtils.DAY_IN_MILLIS)); - } else if(rec.start.getTime() < System.currentTimeMillis() + 1000*60*60*24*6 && - rec.start.getTime() > System.currentTimeMillis() - 1000*60*60*24*2 - ) { - date.setText(new SimpleDateFormat("EEEE").format(rec.start.getTime())); - } else { - date.setText(DateFormat.getDateFormat(date.getContext()).format(rec.start)); - } - - date.invalidate(); - - String msg = ""; - if (rec.error != null) { - msg = rec.error; - state.setImageResource(R.drawable.ic_error_small); - } else if ("completed".equals(rec.state)) { - msg = getString(R.string.pvr_completed); - state.setImageResource(R.drawable.ic_success_small); - } else if ("invalid".equals(rec.state)) { - msg = getString(R.string.pvr_invalid); - state.setImageResource(R.drawable.ic_error_small); - } else if ("missed".equals(rec.state)) { - msg = getString(R.string.pvr_missed); - state.setImageResource(R.drawable.ic_error_small); - } else if ("recording".equals(rec.state)) { - msg = getString(R.string.pvr_recording); - state.setImageResource(R.drawable.ic_rec_small); - } else if ("scheduled".equals(rec.state)) { - msg = getString(R.string.pvr_scheduled); - state.setImageResource(R.drawable.ic_schedule_small); - } else { - state.setImageDrawable(null); - } - if (msg.length() > 0) { - message.setText("(" + msg + ")"); - } else { - message.setText(msg); - } - message.invalidate(); - - desc.setText(rec.description); - desc.invalidate(); - - icon.invalidate(); - - time.setText( - DateFormat.getTimeFormat(time.getContext()).format(rec.start) - + " - " - + DateFormat.getTimeFormat(time.getContext()).format(rec.stop)); - time.invalidate(); - } - } - - class RecordingListAdapter extends ArrayAdapter { - - Activity context; - List list; - - RecordingListAdapter(Activity context, List list) { - super(context, R.layout.recording_list_widget, list); - this.context = context; - this.list = list; - } - - public void sort() { - sort(new Comparator() { - - public int compare(Recording x, Recording y) { - return x.compareTo(y); - } - }); - } - - public void updateView(ListView listView, Recording recording) { - for (int i = 0; i < listView.getChildCount(); i++) { - View view = listView.getChildAt(i); - int pos = listView.getPositionForView(view); - Recording rec = (Recording) listView.getItemAtPosition(pos); - - if (view.getTag() == null || rec == null) { - continue; - } - - if (recording.id != rec.id) { - continue; - } - - ViewWarpper wrapper = (ViewWarpper) view.getTag(); - wrapper.repaint(recording); - } - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View row = convertView; - ViewWarpper wrapper = null; - - Recording rec = list.get(position); - - if (row == null) { - LayoutInflater inflater = context.getLayoutInflater(); - row = inflater.inflate(R.layout.recording_list_widget, null, false); - - wrapper = new ViewWarpper(row); - row.setTag(wrapper); - - } else { - wrapper = (ViewWarpper) row.getTag(); - } - - wrapper.repaint(rec); - return row; - } - } + private RecordingListAdapter recAdapter; + + @Override + public void onCreate(Bundle icicle) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + Boolean theme = prefs.getBoolean("lightThemePref", false); + setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme); + + super.onCreate(icicle); + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + + requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); + + List recList = new ArrayList(); + recList.addAll(app.getRecordings()); + recAdapter = new RecordingListAdapter(this, recList); + recAdapter.sort(); + setListAdapter(recAdapter); + registerForContextMenu(getListView()); + getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, + R.layout.recording_list_title); + TextView t = (TextView) findViewById(R.id.ct_title); + + t.setText(R.string.menu_recordings); + } + + @Override + protected void onResume() { + super.onResume(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.addListener(this); + } + + @Override + protected void onPause() { + super.onPause(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.removeListener(this); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Recording rec = (Recording) recAdapter.getItem(position); + + Intent intent = new Intent(this, RecordingActivity.class); + intent.putExtra("id", rec.id); + startActivity(intent); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + + MenuItem item = null; + Intent intent = null; + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + Recording rec = recAdapter.getItem(info.position); + + menu.setHeaderTitle(rec.title); + + intent = new Intent(RecordingListActivity.this, HTSService.class); + intent.putExtra("id", rec.id); + + if (rec.isRecording() || rec.isScheduled()) { + intent.setAction(HTSService.ACTION_DVR_CANCEL); + item = menu.add(ContextMenu.NONE, R.string.menu_record_cancel, + ContextMenu.NONE, R.string.menu_record_cancel); + item.setIntent(intent); + } else { + intent.setAction(HTSService.ACTION_DVR_DELETE); + item = menu.add(ContextMenu.NONE, R.string.menu_record_remove, + ContextMenu.NONE, R.string.menu_record_remove); + item.setIntent(intent); + + item = menu.add(ContextMenu.NONE, R.string.ch_play, + ContextMenu.NONE, R.string.ch_play); + intent = new Intent(this, ExternalPlaybackActivity.class); + intent.putExtra("dvrId", rec.id); + item.setIntent(intent); + item.setIcon(android.R.drawable.ic_menu_view); + } + + item = menu.add(ContextMenu.NONE, R.string.search_hint, + ContextMenu.NONE, R.string.search_hint); + item.setIntent(new SearchEPGIntent(this, rec.title)); + item.setIcon(android.R.drawable.ic_menu_search); + + item = menu.add(ContextMenu.NONE, ContextMenu.NONE, ContextMenu.NONE, + "IMDb"); + item.setIntent(new SearchIMDbIntent(this, rec.title)); + item.setIcon(android.R.drawable.ic_menu_info_details); + } + + @Override + public boolean onContextItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.string.menu_record: + case R.string.menu_record_cancel: { + startService(item.getIntent()); + return true; + } + case R.string.menu_record_remove: { + + new AlertDialog.Builder(this) + .setTitle(R.string.menu_record_remove) + .setPositiveButton(android.R.string.yes, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + startService(item.getIntent()); + } + }) + .setNegativeButton(android.R.string.no, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + // NOP + } + }).show(); + + return true; + } + default: { + return super.onContextItemSelected(item); + } + } + } + + public void onMessage(String action, final Object obj) { + if (action.equals(TVHGuideApplication.ACTION_LOADING) && !(Boolean) obj) { + + runOnUiThread(new Runnable() { + + public void run() { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + recAdapter.list.clear(); + recAdapter.list.addAll(app.getRecordings()); + recAdapter.notifyDataSetChanged(); + recAdapter.sort(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_DVR_ADD)) { + runOnUiThread(new Runnable() { + + public void run() { + recAdapter.add((Recording) obj); + recAdapter.notifyDataSetChanged(); + recAdapter.sort(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_DVR_DELETE)) { + runOnUiThread(new Runnable() { + + public void run() { + recAdapter.remove((Recording) obj); + recAdapter.notifyDataSetChanged(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_DVR_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Recording rec = (Recording) obj; + recAdapter.updateView(getListView(), rec); + } + }); + } + } + + private class ViewWarpper { + + TextView title; + TextView channel; + TextView time; + TextView date; + TextView message; + TextView desc; + ImageView icon; + ImageView state; + + public ViewWarpper(View base) { + title = (TextView) base.findViewById(R.id.rec_title); + channel = (TextView) base.findViewById(R.id.rec_channel); + + time = (TextView) base.findViewById(R.id.rec_time); + date = (TextView) base.findViewById(R.id.rec_date); + message = (TextView) base.findViewById(R.id.rec_message); + desc = (TextView) base.findViewById(R.id.rec_desc); + icon = (ImageView) base.findViewById(R.id.rec_icon); + state = (ImageView) base.findViewById(R.id.rec_state); + } + + public void repaint(Recording rec) { + Channel ch = rec.channel; + + title.setText(rec.title); + title.invalidate(); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(icon.getContext()); + Boolean showIcons = prefs.getBoolean("showIconPref", false); + icon.setVisibility(showIcons ? ImageView.VISIBLE : ImageView.GONE); + icon.setImageBitmap(ch.iconBitmap); + + channel.setText(ch.name); + channel.invalidate(); + + if (DateUtils.isToday(rec.start.getTime())) { + date.setText(getString(R.string.today)); + } else if (rec.start.getTime() < System.currentTimeMillis() + 1000 + * 60 * 60 * 24 * 2 + && rec.start.getTime() > System.currentTimeMillis() - 1000 + * 60 * 60 * 24 * 2) { + date.setText(DateUtils.getRelativeTimeSpanString( + rec.start.getTime(), System.currentTimeMillis(), + DateUtils.DAY_IN_MILLIS)); + } else if (rec.start.getTime() < System.currentTimeMillis() + 1000 + * 60 * 60 * 24 * 6 + && rec.start.getTime() > System.currentTimeMillis() - 1000 + * 60 * 60 * 24 * 2) { + date.setText(new SimpleDateFormat("EEEE", Locale.getDefault()) + .format(rec.start.getTime())); + } else { + date.setText(DateFormat.getDateFormat(date.getContext()) + .format(rec.start)); + } + + date.invalidate(); + + String msg = ""; + if (rec.error != null) { + msg = rec.error; + state.setImageResource(R.drawable.ic_error_small); + } else if ("completed".equals(rec.state)) { + msg = getString(R.string.pvr_completed); + state.setImageResource(R.drawable.ic_success_small); + } else if ("invalid".equals(rec.state)) { + msg = getString(R.string.pvr_invalid); + state.setImageResource(R.drawable.ic_error_small); + } else if ("missed".equals(rec.state)) { + msg = getString(R.string.pvr_missed); + state.setImageResource(R.drawable.ic_error_small); + } else if ("recording".equals(rec.state)) { + msg = getString(R.string.pvr_recording); + state.setImageResource(R.drawable.ic_rec_small); + } else if ("scheduled".equals(rec.state)) { + msg = getString(R.string.pvr_scheduled); + state.setImageResource(R.drawable.ic_schedule_small); + } else { + state.setImageDrawable(null); + } + if (msg.length() > 0) { + message.setText("(" + msg + ")"); + } else { + message.setText(msg); + } + message.invalidate(); + + desc.setText(rec.description); + desc.invalidate(); + + icon.invalidate(); + + time.setText(DateFormat.getTimeFormat(time.getContext()).format( + rec.start) + + " - " + + DateFormat.getTimeFormat(time.getContext()).format( + rec.stop)); + time.invalidate(); + } + } + + class RecordingListAdapter extends ArrayAdapter { + + Activity context; + List list; + + RecordingListAdapter(Activity context, List list) { + super(context, R.layout.recording_list_widget, list); + this.context = context; + this.list = list; + } + + public void sort() { + sort(new Comparator() { + + public int compare(Recording x, Recording y) { + return x.compareTo(y); + } + }); + } + + public void updateView(ListView listView, Recording recording) { + for (int i = 0; i < listView.getChildCount(); i++) { + View view = listView.getChildAt(i); + int pos = listView.getPositionForView(view); + Recording rec = (Recording) listView.getItemAtPosition(pos); + + if (view.getTag() == null || rec == null) { + continue; + } + + if (recording.id != rec.id) { + continue; + } + + ViewWarpper wrapper = (ViewWarpper) view.getTag(); + wrapper.repaint(recording); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + ViewWarpper wrapper = null; + + Recording rec = list.get(position); + + if (row == null) { + LayoutInflater inflater = context.getLayoutInflater(); + row = inflater.inflate(R.layout.recording_list_widget, null, + false); + + wrapper = new ViewWarpper(row); + row.setTag(wrapper); + + } else { + wrapper = (ViewWarpper) row.getTag(); + } + + wrapper.repaint(rec); + return row; + } + } } diff --git a/src/org/tvheadend/tvhguide/SearchResultActivity.java b/src/org/tvheadend/tvhguide/SearchResultActivity.java index 4ab31a3..2f7eca5 100644 --- a/src/org/tvheadend/tvhguide/SearchResultActivity.java +++ b/src/org/tvheadend/tvhguide/SearchResultActivity.java @@ -18,6 +18,21 @@ */ package org.tvheadend.tvhguide; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; + +import org.tvheadend.tvhguide.R.string; +import org.tvheadend.tvhguide.htsp.HTSListener; +import org.tvheadend.tvhguide.htsp.HTSService; +import org.tvheadend.tvhguide.model.Channel; +import org.tvheadend.tvhguide.model.Programme; +import org.tvheadend.tvhguide.model.Recording; +import org.tvheadend.tvhguide.model.SeriesInfo; + import android.app.Activity; import android.app.ListActivity; import android.app.SearchManager; @@ -41,237 +56,229 @@ import android.widget.ListView; import android.widget.TextView; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.regex.Pattern; - -import org.tvheadend.tvhguide.R; -import org.tvheadend.tvhguide.R.string; -import org.tvheadend.tvhguide.htsp.HTSListener; -import org.tvheadend.tvhguide.htsp.HTSService; -import org.tvheadend.tvhguide.model.Channel; -import org.tvheadend.tvhguide.model.Programme; -import org.tvheadend.tvhguide.model.Recording; -import org.tvheadend.tvhguide.model.SeriesInfo; - /** - * + * * @author john-tornblom */ public class SearchResultActivity extends ListActivity implements HTSListener { - private SearchResultAdapter srAdapter; - private SparseArray contentTypes; - private Pattern pattern; - private Channel channel; - - @Override - public void onCreate(Bundle icicle) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - Boolean theme = prefs.getBoolean("lightThemePref", false); - setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme); - - super.onCreate(icicle); - - requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); - - registerForContextMenu(getListView()); - - contentTypes = TVHGuideApplication.getContentTypes(this); - - List srList = new ArrayList(); - srAdapter = new SearchResultAdapter(this, srList); - srAdapter.sort(); - setListAdapter(srAdapter); - - getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.search_result_title); - - View v = findViewById(R.id.ct_btn); - v.setOnClickListener(new android.view.View.OnClickListener() { - - public void onClick(View arg0) { - onSearchRequested(); - } - }); - - onNewIntent(getIntent()); - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - - if (!Intent.ACTION_SEARCH.equals(intent.getAction()) - || !intent.hasExtra(SearchManager.QUERY)) { - return; - } - - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - - Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA); - if (appData != null) { - channel = app.getChannel(appData.getLong("channelId")); - } else { - channel = null; - } - - srAdapter.clear(); - - String query = intent.getStringExtra(SearchManager.QUERY); - pattern = Pattern.compile(query, Pattern.CASE_INSENSITIVE); - intent = new Intent(SearchResultActivity.this, HTSService.class); - intent.setAction(HTSService.ACTION_EPG_QUERY); - intent.putExtra("query", query); - if (channel != null) { - intent.putExtra("channelId", channel.id); - } - - startService(intent); - - if (channel == null) { - for (Channel ch : app.getChannels()) { - for (Programme p : ch.epg) { - if (pattern.matcher(p.title).find()) { - srAdapter.add(p); - } - } - } - } else { - for (Programme p : channel.epg) { - if (pattern.matcher(p.title).find()) { - srAdapter.add(p); - } - } - } - - ImageView iv = (ImageView) findViewById(R.id.ct_logo); - if (channel != null && channel.iconBitmap != null) { - iv.setImageBitmap(channel.iconBitmap); - } else { - iv.setImageResource(R.drawable.logo_72); - } - - TextView t = (TextView) findViewById(R.id.ct_title); - t.setText(this.getString(android.R.string.search_go) + ": " + query); - } - - @Override - protected void onResume() { - super.onResume(); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.addListener(this); - } - - @Override - protected void onPause() { - super.onPause(); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.removeListener(this); - } - - @Override - protected void onListItemClick(ListView l, View v, int position, long id) { - Programme p = (Programme) srAdapter.getItem(position); - - Intent intent = new Intent(this, ProgrammeActivity.class); - intent.putExtra("eventId", p.id); - intent.putExtra("channelId", p.channel.id); - startActivity(intent); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.string.menu_record: - case R.string.menu_record_cancel: - case R.string.menu_record_remove: { - startService(item.getIntent()); - return true; - } - default: { - return super.onContextItemSelected(item); - } - } - } - - @Override - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - super.onCreateContextMenu(menu, v, menuInfo); - - AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; - Programme p = srAdapter.getItem(info.position); - - menu.setHeaderTitle(p.title); - - Intent intent = new Intent(this, HTSService.class); - - MenuItem item = null; - - if (p.recording == null) { - intent.setAction(HTSService.ACTION_DVR_ADD); - intent.putExtra("eventId", p.id); - intent.putExtra("channelId", p.channel.id); - item = menu.add(ContextMenu.NONE, R.string.menu_record, ContextMenu.NONE, R.string.menu_record); - } else if ("recording".equals(p.recording.state) || "scheduled".equals(p.recording.state)) { - intent.setAction(HTSService.ACTION_DVR_CANCEL); - intent.putExtra("id", p.recording.id); - item = menu.add(ContextMenu.NONE, R.string.menu_record_cancel, ContextMenu.NONE, R.string.menu_record_cancel); - } else { - intent.setAction(HTSService.ACTION_DVR_DELETE); - intent.putExtra("id", p.recording.id); - item = menu.add(ContextMenu.NONE, R.string.menu_record_remove, ContextMenu.NONE, R.string.menu_record_remove); - } - - item.setIntent(intent); - } - - public void onMessage(String action, final Object obj) { - if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_ADD)) { - runOnUiThread(new Runnable() { - - public void run() { - Programme p = (Programme) obj; - if (pattern != null && pattern.matcher(p.title).find()) { - srAdapter.add(p); - srAdapter.notifyDataSetChanged(); - srAdapter.sort(); - } - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_DELETE)) { - runOnUiThread(new Runnable() { - - public void run() { - Programme p = (Programme) obj; - srAdapter.remove(p); - srAdapter.notifyDataSetChanged(); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_UPDATE)) { - runOnUiThread(new Runnable() { - - public void run() { - Programme p = (Programme) obj; - srAdapter.updateView(getListView(), p); - } - }); - } else if (action.equals(TVHGuideApplication.ACTION_DVR_UPDATE)) { - runOnUiThread(new Runnable() { - - public void run() { - Recording rec = (Recording) obj; - for (Programme p : srAdapter.list) { - if (rec == p.recording) { - srAdapter.updateView(getListView(), p); - return; - } - } - } - }); - } - } + private SearchResultAdapter srAdapter; + private SparseArray contentTypes; + private Pattern pattern; + private Channel channel; + + @Override + public void onCreate(Bundle icicle) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + Boolean theme = prefs.getBoolean("lightThemePref", false); + setTheme(theme ? R.style.CustomTheme_Light : R.style.CustomTheme); + + super.onCreate(icicle); + + requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); + + registerForContextMenu(getListView()); + + contentTypes = TVHGuideApplication.getContentTypes(this.getResources()); + + List srList = new ArrayList(); + srAdapter = new SearchResultAdapter(this, srList); + srAdapter.sort(); + setListAdapter(srAdapter); + + getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, + R.layout.search_result_title); + + View v = findViewById(R.id.ct_btn); + v.setOnClickListener(new android.view.View.OnClickListener() { + + public void onClick(View arg0) { + onSearchRequested(); + } + }); + + onNewIntent(getIntent()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + if (!Intent.ACTION_SEARCH.equals(intent.getAction()) + || !intent.hasExtra(SearchManager.QUERY)) { + return; + } + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + + Bundle appData = intent.getBundleExtra(SearchManager.APP_DATA); + if (appData != null) { + channel = app.getChannel(appData.getLong("channelId")); + } else { + channel = null; + } + + srAdapter.clear(); + + String query = intent.getStringExtra(SearchManager.QUERY); + pattern = Pattern.compile(query, Pattern.CASE_INSENSITIVE); + intent = new Intent(SearchResultActivity.this, HTSService.class); + intent.setAction(HTSService.ACTION_EPG_QUERY); + intent.putExtra("query", query); + if (channel != null) { + intent.putExtra("channelId", channel.id); + } + + startService(intent); + + if (channel == null) { + for (Channel ch : app.getChannels()) { + for (Programme p : ch.epg) { + if (pattern.matcher(p.title).find()) { + srAdapter.add(p); + } + } + } + } else { + for (Programme p : channel.epg) { + if (pattern.matcher(p.title).find()) { + srAdapter.add(p); + } + } + } + + ImageView iv = (ImageView) findViewById(R.id.ct_logo); + if (channel != null && channel.iconBitmap != null) { + iv.setImageBitmap(channel.iconBitmap); + } else { + iv.setImageResource(R.drawable.logo_72); + } + + TextView t = (TextView) findViewById(R.id.ct_title); + t.setText(this.getString(android.R.string.search_go) + ": " + query); + } + + @Override + protected void onResume() { + super.onResume(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.addListener(this); + } + + @Override + protected void onPause() { + super.onPause(); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.removeListener(this); + } + + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + Programme p = (Programme) srAdapter.getItem(position); + + Intent intent = new Intent(this, ProgrammeActivity.class); + intent.putExtra("eventId", p.id); + intent.putExtra("channelId", p.channel.id); + startActivity(intent); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.string.menu_record: + case R.string.menu_record_cancel: + case R.string.menu_record_remove: { + startService(item.getIntent()); + return true; + } + default: { + return super.onContextItemSelected(item); + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo; + Programme p = srAdapter.getItem(info.position); + + menu.setHeaderTitle(p.title); + + Intent intent = new Intent(this, HTSService.class); + + MenuItem item = null; + + if (p.recording == null) { + intent.setAction(HTSService.ACTION_DVR_ADD); + intent.putExtra("eventId", p.id); + intent.putExtra("channelId", p.channel.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record, + ContextMenu.NONE, R.string.menu_record); + } else if ("recording".equals(p.recording.state) + || "scheduled".equals(p.recording.state)) { + intent.setAction(HTSService.ACTION_DVR_CANCEL); + intent.putExtra("id", p.recording.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record_cancel, + ContextMenu.NONE, R.string.menu_record_cancel); + } else { + intent.setAction(HTSService.ACTION_DVR_DELETE); + intent.putExtra("id", p.recording.id); + item = menu.add(ContextMenu.NONE, R.string.menu_record_remove, + ContextMenu.NONE, R.string.menu_record_remove); + } + + item.setIntent(intent); + } + + public void onMessage(String action, final Object obj) { + if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_ADD)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + if (pattern != null && pattern.matcher(p.title).find()) { + srAdapter.add(p); + srAdapter.notifyDataSetChanged(); + srAdapter.sort(); + } + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_DELETE)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + srAdapter.remove(p); + srAdapter.notifyDataSetChanged(); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_PROGRAMME_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Programme p = (Programme) obj; + srAdapter.updateView(getListView(), p); + } + }); + } else if (action.equals(TVHGuideApplication.ACTION_DVR_UPDATE)) { + runOnUiThread(new Runnable() { + + public void run() { + Recording rec = (Recording) obj; + for (Programme p : srAdapter.list) { + if (rec == p.recording) { + srAdapter.updateView(getListView(), p); + return; + } + } + } + }); + } + } public String buildSeriesInfoString(SeriesInfo info) { if (info.onScreen != null && info.onScreen.length() > 0) @@ -281,184 +288,198 @@ public String buildSeriesInfoString(SeriesInfo info) { String season = this.getResources().getString(string.pr_season); String episode = this.getResources().getString(string.pr_episode); String part = this.getResources().getString(string.pr_part); - - if(info.onScreen.length() > 0) { + + if (info.onScreen.length() > 0) { return info.onScreen; } - + if (info.seasonNumber > 0) { if (s.length() > 0) s += ", "; - s += String.format("%s %02d", season.toLowerCase(), info.seasonNumber); + s += String.format("%s %02d", + season.toLowerCase(Locale.getDefault()), info.seasonNumber); } if (info.episodeNumber > 0) { if (s.length() > 0) s += ", "; - s += String.format("%s %02d", episode.toLowerCase(), info.episodeNumber); + s += String.format("%s %02d", + episode.toLowerCase(Locale.getDefault()), + info.episodeNumber); } if (info.partNumber > 0) { if (s.length() > 0) s += ", "; - s += String.format("%s %d", part.toLowerCase(), info.partNumber); + s += String.format("%s %d", part.toLowerCase(Locale.getDefault()), + info.partNumber); } - if(s.length() > 0) { - s = s.substring(0,1).toUpperCase() + s.substring(1); + if (s.length() > 0) { + s = s.substring(0, 1).toUpperCase(Locale.getDefault()) + + s.substring(1); } - + return s; } - - private class ViewWarpper { - - TextView title; - TextView channel; - TextView time; - TextView date; - TextView description; - ImageView icon; - ImageView state; - - public ViewWarpper(View base) { - title = (TextView) base.findViewById(R.id.sr_title); - channel = (TextView) base.findViewById(R.id.sr_channel); - description = (TextView) base.findViewById(R.id.sr_desc); - - time = (TextView) base.findViewById(R.id.sr_time); - date = (TextView) base.findViewById(R.id.sr_date); - - icon = (ImageView) base.findViewById(R.id.sr_icon); - state = (ImageView) base.findViewById(R.id.sr_state); - } - - public void repaint(Programme p) { - Channel ch = p.channel; - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(icon.getContext()); - Boolean showIcons = prefs.getBoolean("showIconPref", false); - icon.setVisibility(showIcons ? ImageView.VISIBLE : ImageView.GONE); - icon.setImageBitmap(ch.iconBitmap); - - title.setText(p.title); - - if (p.recording == null) { - state.setImageDrawable(null); - } else if (p.recording.error != null) { - state.setImageResource(R.drawable.ic_error_small); - } else if ("completed".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_success_small); - } else if ("invalid".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_error_small); - } else if ("missed".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_error_small); - } else if ("recording".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_rec_small); - } else if ("scheduled".equals(p.recording.state)) { - state.setImageResource(R.drawable.ic_schedule_small); - } else { - state.setImageDrawable(null); - } - - title.invalidate(); - - String s = buildSeriesInfoString(p.seriesInfo); - if(s.length() == 0) { - s = p.description; - } - - description.setText(s); - description.invalidate(); - - String contentType = contentTypes.get(p.contentType, ""); - if (contentType.length() > 0) { - channel.setText(ch.name + " (" + contentType + ")"); - } else { - channel.setText(ch.name); - } - channel.invalidate(); - - if (DateUtils.isToday(p.start.getTime())) { - date.setText(getString(R.string.today)); - } else if(p.start.getTime() < System.currentTimeMillis() + 1000*60*60*24*2 && - p.start.getTime() > System.currentTimeMillis() - 1000*60*60*24*2) { - date.setText(DateUtils.getRelativeTimeSpanString(p.start.getTime(), - System.currentTimeMillis(), DateUtils.DAY_IN_MILLIS)); - } else if(p.start.getTime() < System.currentTimeMillis() + 1000*60*60*24*6 && - p.start.getTime() > System.currentTimeMillis() - 1000*60*60*24*2) { - date.setText(new SimpleDateFormat("EEEE").format(p.start.getTime())); - } else { - date.setText(DateFormat.getDateFormat(date.getContext()).format(p.start)); - } - - date.invalidate(); - - - time.setText( - DateFormat.getTimeFormat(time.getContext()).format(p.start) - + " - " - + DateFormat.getTimeFormat(time.getContext()).format(p.stop)); - time.invalidate(); - } - } - - class SearchResultAdapter extends ArrayAdapter { - - Activity context; - List list; - - SearchResultAdapter(Activity context, List list) { - super(context, R.layout.search_result_widget, list); - this.context = context; - this.list = list; - } - - public void sort() { - sort(new Comparator() { - - public int compare(Programme x, Programme y) { - return x.compareTo(y); - } - }); - } - - public void updateView(ListView listView, Programme programme) { - for (int i = 0; i < listView.getChildCount(); i++) { - View view = listView.getChildAt(i); - int pos = listView.getPositionForView(view); - Programme pr = (Programme) listView.getItemAtPosition(pos); - - if (view.getTag() == null || pr == null) { - continue; - } - - if (programme.id != pr.id) { - continue; - } - - ViewWarpper wrapper = (ViewWarpper) view.getTag(); - wrapper.repaint(programme); - } - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View row = convertView; - ViewWarpper wrapper = null; - - if (row == null) { - LayoutInflater inflater = context.getLayoutInflater(); - row = inflater.inflate(R.layout.search_result_widget, null, false); - - wrapper = new ViewWarpper(row); - row.setTag(wrapper); - - } else { - wrapper = (ViewWarpper) row.getTag(); - } - - Programme p = getItem(position); - wrapper.repaint(p); - return row; - } - } + + private class ViewWarpper { + + TextView title; + TextView channel; + TextView time; + TextView date; + TextView description; + ImageView icon; + ImageView state; + + public ViewWarpper(View base) { + title = (TextView) base.findViewById(R.id.sr_title); + channel = (TextView) base.findViewById(R.id.sr_channel); + description = (TextView) base.findViewById(R.id.sr_desc); + + time = (TextView) base.findViewById(R.id.sr_time); + date = (TextView) base.findViewById(R.id.sr_date); + + icon = (ImageView) base.findViewById(R.id.sr_icon); + state = (ImageView) base.findViewById(R.id.sr_state); + } + + public void repaint(Programme p) { + Channel ch = p.channel; + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(icon.getContext()); + Boolean showIcons = prefs.getBoolean("showIconPref", false); + icon.setVisibility(showIcons ? ImageView.VISIBLE : ImageView.GONE); + icon.setImageBitmap(ch.iconBitmap); + + title.setText(p.title); + + if (p.recording == null) { + state.setImageDrawable(null); + } else if (p.recording.error != null) { + state.setImageResource(R.drawable.ic_error_small); + } else if ("completed".equals(p.recording.state)) { + state.setImageResource(R.drawable.ic_success_small); + } else if ("invalid".equals(p.recording.state)) { + state.setImageResource(R.drawable.ic_error_small); + } else if ("missed".equals(p.recording.state)) { + state.setImageResource(R.drawable.ic_error_small); + } else if ("recording".equals(p.recording.state)) { + state.setImageResource(R.drawable.ic_rec_small); + } else if ("scheduled".equals(p.recording.state)) { + state.setImageResource(R.drawable.ic_schedule_small); + } else { + state.setImageDrawable(null); + } + + title.invalidate(); + + String s = buildSeriesInfoString(p.seriesInfo); + if (s.length() == 0) { + s = p.description; + } + + description.setText(s); + description.invalidate(); + + String contentType = contentTypes.get(p.contentType, ""); + if (contentType.length() > 0) { + channel.setText(ch.name + " (" + contentType + ")"); + } else { + channel.setText(ch.name); + } + channel.invalidate(); + + if (DateUtils.isToday(p.start.getTime())) { + date.setText(getString(R.string.today)); + } else if (p.start.getTime() < System.currentTimeMillis() + 1000 + * 60 * 60 * 24 * 2 + && p.start.getTime() > System.currentTimeMillis() - 1000 + * 60 * 60 * 24 * 2) { + date.setText(DateUtils.getRelativeTimeSpanString( + p.start.getTime(), System.currentTimeMillis(), + DateUtils.DAY_IN_MILLIS)); + } else if (p.start.getTime() < System.currentTimeMillis() + 1000 + * 60 * 60 * 24 * 6 + && p.start.getTime() > System.currentTimeMillis() - 1000 + * 60 * 60 * 24 * 2) { + date.setText(new SimpleDateFormat("EEEE", Locale.getDefault()) + .format(p.start.getTime())); + } else { + date.setText(DateFormat.getDateFormat(date.getContext()) + .format(p.start)); + } + + date.invalidate(); + + time.setText(DateFormat.getTimeFormat(time.getContext()).format( + p.start) + + " - " + + DateFormat.getTimeFormat(time.getContext()) + .format(p.stop)); + time.invalidate(); + } + } + + class SearchResultAdapter extends ArrayAdapter { + + Activity context; + List list; + + SearchResultAdapter(Activity context, List list) { + super(context, R.layout.search_result_widget, list); + this.context = context; + this.list = list; + } + + public void sort() { + sort(new Comparator() { + + public int compare(Programme x, Programme y) { + return x.compareTo(y); + } + }); + } + + public void updateView(ListView listView, Programme programme) { + for (int i = 0; i < listView.getChildCount(); i++) { + View view = listView.getChildAt(i); + int pos = listView.getPositionForView(view); + Programme pr = (Programme) listView.getItemAtPosition(pos); + + if (view.getTag() == null || pr == null) { + continue; + } + + if (programme.id != pr.id) { + continue; + } + + ViewWarpper wrapper = (ViewWarpper) view.getTag(); + wrapper.repaint(programme); + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View row = convertView; + ViewWarpper wrapper = null; + + if (row == null) { + LayoutInflater inflater = context.getLayoutInflater(); + row = inflater.inflate(R.layout.search_result_widget, null, + false); + + wrapper = new ViewWarpper(row); + row.setTag(wrapper); + + } else { + wrapper = (ViewWarpper) row.getTag(); + } + + Programme p = getItem(position); + wrapper.repaint(p); + return row; + } + } } diff --git a/src/org/tvheadend/tvhguide/SettingsActivity.java b/src/org/tvheadend/tvhguide/SettingsActivity.java index 9cea132..841e48a 100644 --- a/src/org/tvheadend/tvhguide/SettingsActivity.java +++ b/src/org/tvheadend/tvhguide/SettingsActivity.java @@ -18,6 +18,8 @@ */ package org.tvheadend.tvhguide; +import org.tvheadend.tvhguide.htsp.HTSService; + import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; @@ -25,66 +27,72 @@ import android.preference.PreferenceManager; import android.util.Log; import android.view.Window; -import org.tvheadend.tvhguide.R; -import org.tvheadend.tvhguide.htsp.HTSService; /** - * + * * @author john-tornblom */ public class SettingsActivity extends PreferenceActivity { - private int oldPort; - private String oldHostname; - private String oldUser; - private String oldPw; + private int oldPort; + private String oldHostname; + private String oldUser; + private String oldPw; - @Override - public void onCreate(Bundle savedInstanceState) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - Boolean theme = prefs.getBoolean("lightThemePref", false); - setTheme(theme ? android.R.style.Theme_Light : android.R.style.Theme); + @SuppressWarnings("deprecation") + @Override + public void onCreate(Bundle savedInstanceState) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + Boolean theme = prefs.getBoolean("lightThemePref", false); + setTheme(theme ? android.R.style.Theme_Light : android.R.style.Theme); - requestWindowFeature(Window.FEATURE_LEFT_ICON); - super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_LEFT_ICON); + super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.preferences); - setTitle(getString(R.string.app_name) + " - " + getString(R.string.menu_settings)); - setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.logo_72); - } + addPreferencesFromResource(R.xml.preferences); + setTitle(getString(R.string.app_name) + " - " + + getString(R.string.menu_settings)); + setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, R.drawable.logo_72); + } - @Override - protected void onStart() { - super.onStart(); + @Override + protected void onStart() { + super.onStart(); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - oldHostname = prefs.getString("serverHostPref", ""); - oldPort = Integer.parseInt(prefs.getString("serverPortPref", "")); - oldUser = prefs.getString("usernamePref", ""); - oldPw = prefs.getString("passwordPref", ""); - } + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + oldHostname = prefs.getString("serverHostPref", "mediaserver"); + oldPort = Integer.parseInt(prefs.getString("serverPortPref", "")); + oldUser = prefs.getString("usernamePref", ""); + oldPw = prefs.getString("passwordPref", ""); + } - @Override - protected void onPause() { - super.onPause(); + @Override + protected void onPause() { + super.onPause(); - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - boolean reconnect = false; - reconnect |= !oldHostname.equals(prefs.getString("serverHostPref", "")); - reconnect |= oldPort != Integer.parseInt(prefs.getString("serverPortPref", "")); - reconnect |= !oldUser.equals(prefs.getString("usernamePref", "")); - reconnect |= !oldPw.equals(prefs.getString("passwordPref", "")); + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + boolean reconnect = false; + reconnect |= !oldHostname.equals(prefs.getString("serverHostPref", "")); + reconnect |= oldPort != Integer.parseInt(prefs.getString( + "serverPortPref", "")); + reconnect |= !oldUser.equals(prefs.getString("usernamePref", "")); + reconnect |= !oldPw.equals(prefs.getString("passwordPref", "")); - if (reconnect) { - Log.d("SettingsActivity", "Connectivity settings chaned, forcing a reconnect"); - Intent intent = new Intent(SettingsActivity.this, HTSService.class); - intent.setAction(HTSService.ACTION_CONNECT); - intent.putExtra("hostname", prefs.getString("serverHostPref", "")); - intent.putExtra("port", Integer.parseInt(prefs.getString("serverPortPref", ""))); - intent.putExtra("username", prefs.getString("usernamePref", "")); - intent.putExtra("password", prefs.getString("passwordPref", "")); - intent.putExtra("force", true); - startService(intent); - } - } + if (reconnect) { + Log.d("SettingsActivity", + "Connectivity settings chaned, forcing a reconnect"); + Intent intent = new Intent(SettingsActivity.this, HTSService.class); + intent.setAction(HTSService.ACTION_CONNECT); + intent.putExtra("hostname", prefs.getString("serverHostPref", "")); + intent.putExtra("port", + Integer.parseInt(prefs.getString("serverPortPref", ""))); + intent.putExtra("username", prefs.getString("usernamePref", "")); + intent.putExtra("password", prefs.getString("passwordPref", "")); + intent.putExtra("force", true); + startService(intent); + } + } } diff --git a/src/org/tvheadend/tvhguide/TVHGuideApplication.java b/src/org/tvheadend/tvhguide/TVHGuideApplication.java index fcb9a0d..6e0ab7a 100644 --- a/src/org/tvheadend/tvhguide/TVHGuideApplication.java +++ b/src/org/tvheadend/tvhguide/TVHGuideApplication.java @@ -18,16 +18,10 @@ */ package org.tvheadend.tvhguide; -import android.app.Application; -import android.content.Context; -import android.os.Handler; -import android.util.SparseArray; -import android.widget.Toast; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.tvheadend.tvhguide.R; import org.tvheadend.tvhguide.htsp.HTSListener; import org.tvheadend.tvhguide.model.Channel; import org.tvheadend.tvhguide.model.ChannelTag; @@ -37,377 +31,419 @@ import org.tvheadend.tvhguide.model.Recording; import org.tvheadend.tvhguide.model.Subscription; +import android.app.Application; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.util.SparseArray; +import android.widget.Toast; + /** - * + * * @author john-tornblom */ public class TVHGuideApplication extends Application { - public static final String ACTION_CHANNEL_ADD = "org.me.tvhguide.CHANNEL_ADD"; - public static final String ACTION_CHANNEL_DELETE = "org.me.tvhguide.CHANNEL_DELETE"; - public static final String ACTION_CHANNEL_UPDATE = "org.me.tvhguide.CHANNEL_UPDATE"; - public static final String ACTION_TAG_ADD = "org.me.tvhguide.TAG_ADD"; - public static final String ACTION_TAG_DELETE = "org.me.tvhguide.TAG_DELETE"; - public static final String ACTION_TAG_UPDATE = "org.me.tvhguide.TAG_UPDATE"; - public static final String ACTION_DVR_ADD = "org.me.tvhguide.DVR_ADD"; - public static final String ACTION_DVR_DELETE = "org.me.tvhguide.DVR_DELETE"; - public static final String ACTION_DVR_UPDATE = "org.me.tvhguide.DVR_UPDATE"; - public static final String ACTION_PROGRAMME_ADD = "org.me.tvhguide.PROGRAMME_ADD"; - public static final String ACTION_PROGRAMME_DELETE = "org.me.tvhguide.PROGRAMME_DELETE"; - public static final String ACTION_PROGRAMME_UPDATE = "org.me.tvhguide.PROGRAMME_UPDATE"; - public static final String ACTION_SUBSCRIPTION_ADD = "org.me.tvhguide.SUBSCRIPTION_ADD"; - public static final String ACTION_SUBSCRIPTION_DELETE = "org.me.tvhguide.SUBSCRIPTION_DELETE"; - public static final String ACTION_SUBSCRIPTION_UPDATE = "org.me.tvhguide.SUBSCRIPTION_UPDATE"; - public static final String ACTION_SIGNAL_STATUS = "org.me.tvhguide.SIGNAL_STATUS"; - public static final String ACTION_PLAYBACK_PACKET = "org.me.tvhguide.PLAYBACK_PACKET"; - public static final String ACTION_LOADING = "org.me.tvhguide.LOADING"; - public static final String ACTION_TICKET_ADD = "org.me.tvhguide.TICKET"; - public static final String ACTION_ERROR = "org.me.tvhguide.ERROR"; - private final List listeners = new ArrayList(); - private final List tags = Collections.synchronizedList(new ArrayList()); - private final List channels = Collections.synchronizedList(new ArrayList()); - private final List recordings = Collections.synchronizedList(new ArrayList()); - private final List subscriptions = Collections.synchronizedList(new ArrayList()); - private volatile boolean loading = false; - private Handler handler = new Handler(); - - public void addListener(HTSListener l) { - listeners.add(l); - } - - public void removeListener(HTSListener l) { - listeners.remove(l); - } - - private void broadcastMessage(String action, Object obj) { - synchronized (listeners) { - for (HTSListener l : listeners) { - l.onMessage(action, obj); - } - } - } - - public void broadcastError(final String error) { - //Don't show error if no views are open - synchronized (listeners) { - if (listeners.isEmpty()) { - return; - } - } - handler.post(new Runnable() { - - public void run() { - - try { - Toast toast = Toast.makeText(TVHGuideApplication.this, error, Toast.LENGTH_LONG); - toast.show(); - } catch (Throwable ex) { - } - } - }); - broadcastMessage(ACTION_ERROR, error); - } - - public void broadcastPacket(Packet p) { - broadcastMessage(ACTION_PLAYBACK_PACKET, p); - } - - public List getChannelTags() { - return tags; - } - - public void addChannelTag(ChannelTag tag) { - tags.add(tag); - - if (!loading) { - broadcastMessage(ACTION_TAG_ADD, tag); - } - } - - public void removeChannelTag(ChannelTag tag) { - tags.remove(tag); - - if (!loading) { - broadcastMessage(ACTION_TAG_DELETE, tag); - } - } - - public void removeChannelTag(long id) { - for (ChannelTag tag : getChannelTags()) { - if (tag.id == id) { - removeChannelTag(tag); - return; - } - } - } - - public ChannelTag getChannelTag(long id) { - for (ChannelTag tag : getChannelTags()) { - if (tag.id == id) { - return tag; - } - } - return null; - } - - public void updateChannelTag(ChannelTag tag) { - if (!loading) { - broadcastMessage(ACTION_TAG_UPDATE, tag); - } - } - - public void addChannel(Channel channel) { - channels.add(channel); - - if (!loading) { - broadcastMessage(ACTION_CHANNEL_ADD, channel); - } - } - - public List getChannels() { - return channels; - } - - public void removeChannel(Channel channel) { - channels.remove(channel); - - if (!loading) { - broadcastMessage(ACTION_CHANNEL_DELETE, channel); - } - } - - public Channel getChannel(long id) { - for (Channel ch : getChannels()) { - if (ch.id == id) { - return ch; - } - } - return null; - } - - public void removeChannel(long id) { - for (Channel ch : getChannels()) { - if (ch.id == id) { - removeChannel(ch); - return; - } - } - } - - public void updateChannel(Channel ch) { - if (!loading) { - broadcastMessage(ACTION_CHANNEL_UPDATE, ch); - } - } - - public void addProgramme(Programme p) { - if (!loading) { - broadcastMessage(ACTION_PROGRAMME_ADD, p); - } - } - - public void removeProgramme(Programme p) { - if (!loading) { - broadcastMessage(ACTION_PROGRAMME_DELETE, p); - } - } - - public void updateProgramme(Programme p) { - if (!loading) { - broadcastMessage(ACTION_PROGRAMME_UPDATE, p); - } - } - - public void addRecording(Recording rec) { - recordings.add(rec); - - if (!loading) { - broadcastMessage(ACTION_DVR_ADD, rec); - } - } - - public List getRecordings() { - return recordings; - } - - public void removeRecording(Recording rec) { - recordings.remove(rec); - - if (!loading) { - broadcastMessage(ACTION_DVR_DELETE, rec); - } - } - - public Recording getRecording(long id) { - for (Recording rec : getRecordings()) { - if (rec.id == id) { - return rec; - } - } - return null; - } - - public void removeRecording(long id) { - for (Recording rec : getRecordings()) { - if (rec.id == id) { - removeRecording(rec); - return; - } - } - } - - public void updateRecording(Recording rec) { - if (!loading) { - broadcastMessage(ACTION_DVR_UPDATE, rec); - } - } - - public void setLoading(boolean b) { - if (loading != b) { - broadcastMessage(ACTION_LOADING, b); - } - loading = b; - } - - public void clearAll() { - tags.clear(); - recordings.clear(); - - for (Channel ch : channels) { - ch.epg.clear(); - ch.recordings.clear(); - } - channels.clear(); - - for (Subscription s : subscriptions) { - s.streams.clear(); - } - subscriptions.clear(); - - ChannelTag tag = new ChannelTag(); - tag.id = 0; - tag.name = getString(R.string.pr_all_channels); - tags.add(tag); - } - - public void addSubscription(Subscription s) { - subscriptions.add(s); - - if (!loading) { - broadcastMessage(ACTION_SUBSCRIPTION_ADD, s); - } - } - - public List getSubscriptions() { - return subscriptions; - } - - public void removeSubscription(Subscription s) { - s.streams.clear(); - subscriptions.remove(s); - - if (!loading) { - broadcastMessage(ACTION_SUBSCRIPTION_DELETE, s); - } - } - - public Subscription getSubscription(long id) { - for (Subscription s : getSubscriptions()) { - if (s.id == id) { - return s; - } - } - return null; - } - - public void removeSubscription(long id) { - for (Subscription s : getSubscriptions()) { - if (s.id == id) { - removeSubscription(s); - return; - } - } - } - - public void updateSubscription(Subscription s) { - if (!loading) { - broadcastMessage(ACTION_SUBSCRIPTION_UPDATE, s); - } - } - - - public void addTicket(HttpTicket t) { - broadcastMessage(ACTION_TICKET_ADD, t); - } - - public boolean isLoading() { - return loading; - } - - - public static SparseArray getContentTypes(Context ctx) { + public static final String ACTION_CHANNEL_ADD = "org.me.tvhguide.CHANNEL_ADD"; + public static final String ACTION_CHANNEL_DELETE = "org.me.tvhguide.CHANNEL_DELETE"; + public static final String ACTION_CHANNEL_UPDATE = "org.me.tvhguide.CHANNEL_UPDATE"; + public static final String ACTION_TAG_ADD = "org.me.tvhguide.TAG_ADD"; + public static final String ACTION_TAG_DELETE = "org.me.tvhguide.TAG_DELETE"; + public static final String ACTION_TAG_UPDATE = "org.me.tvhguide.TAG_UPDATE"; + public static final String ACTION_DVR_ADD = "org.me.tvhguide.DVR_ADD"; + public static final String ACTION_DVR_DELETE = "org.me.tvhguide.DVR_DELETE"; + public static final String ACTION_DVR_UPDATE = "org.me.tvhguide.DVR_UPDATE"; + public static final String ACTION_PROGRAMME_ADD = "org.me.tvhguide.PROGRAMME_ADD"; + public static final String ACTION_PROGRAMME_DELETE = "org.me.tvhguide.PROGRAMME_DELETE"; + public static final String ACTION_PROGRAMME_UPDATE = "org.me.tvhguide.PROGRAMME_UPDATE"; + public static final String ACTION_SUBSCRIPTION_ADD = "org.me.tvhguide.SUBSCRIPTION_ADD"; + public static final String ACTION_SUBSCRIPTION_DELETE = "org.me.tvhguide.SUBSCRIPTION_DELETE"; + public static final String ACTION_SUBSCRIPTION_UPDATE = "org.me.tvhguide.SUBSCRIPTION_UPDATE"; + public static final String ACTION_SIGNAL_STATUS = "org.me.tvhguide.SIGNAL_STATUS"; + public static final String ACTION_PLAYBACK_PACKET = "org.me.tvhguide.PLAYBACK_PACKET"; + public static final String ACTION_LOADING = "org.me.tvhguide.LOADING"; + public static final String ACTION_TICKET_ADD = "org.me.tvhguide.TICKET"; + public static final String ACTION_ERROR = "org.me.tvhguide.ERROR"; + private final List listeners = new ArrayList(); + private final List tags = Collections + .synchronizedList(new ArrayList()); + private final List channels = Collections + .synchronizedList(new ArrayList()); + private final List recordings = Collections + .synchronizedList(new ArrayList()); + private final List subscriptions = Collections + .synchronizedList(new ArrayList()); + private volatile boolean loading = false; + private Handler handler = new Handler(); + private ChannelTag m_currentTag; + private long m_selectedChannelTagId; + + private static final String PARAM_CHANNEL_TAG = "channel.tag"; + + public void addListener(HTSListener l) { + listeners.add(l); + } + + public void removeListener(HTSListener l) { + listeners.remove(l); + } + + private void broadcastMessage(String action, Object obj) { + synchronized (listeners) { + for (HTSListener l : listeners) { + l.onMessage(action, obj); + } + } + } + + @Override + public void onCreate() { + super.onCreate(); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(this); + m_selectedChannelTagId = prefs.getLong(PARAM_CHANNEL_TAG, -1); + } + + public void broadcastError(final String error) { + // Don't show error if no views are open + synchronized (listeners) { + if (listeners.isEmpty()) { + return; + } + } + handler.post(new Runnable() { + + public void run() { + + try { + Toast toast = Toast.makeText(TVHGuideApplication.this, + error, Toast.LENGTH_LONG); + toast.show(); + } catch (Throwable ex) { + } + } + }); + broadcastMessage(ACTION_ERROR, error); + } + + public void broadcastPacket(Packet p) { + broadcastMessage(ACTION_PLAYBACK_PACKET, p); + } + + public List getChannelTags() { + return tags; + } + + public void addChannelTag(ChannelTag tag) { + tags.add(tag); + + if (tag.id == m_selectedChannelTagId) { + m_currentTag = tag; + } + + if (!loading) { + broadcastMessage(ACTION_TAG_ADD, tag); + } + } + + public void removeChannelTag(ChannelTag tag) { + tags.remove(tag); + + if (!loading) { + broadcastMessage(ACTION_TAG_DELETE, tag); + } + } + + public void removeChannelTag(long id) { + for (ChannelTag tag : getChannelTags()) { + if (tag.id == id) { + removeChannelTag(tag); + return; + } + } + } + + public ChannelTag getChannelTag(long id) { + for (ChannelTag tag : getChannelTags()) { + if (tag.id == id) { + return tag; + } + } + return null; + } + + public void updateChannelTag(ChannelTag tag) { + if (!loading) { + broadcastMessage(ACTION_TAG_UPDATE, tag); + } + } + + public void addChannel(Channel channel) { + channels.add(channel); + + if (!loading) { + broadcastMessage(ACTION_CHANNEL_ADD, channel); + } + } + + public List getChannels() { + return channels; + } + + public void removeChannel(Channel channel) { + channels.remove(channel); + + if (!loading) { + broadcastMessage(ACTION_CHANNEL_DELETE, channel); + } + } + + public Channel getChannel(long id) { + for (Channel ch : getChannels()) { + if (ch.id == id) { + return ch; + } + } + return null; + } + + public void removeChannel(long id) { + for (Channel ch : getChannels()) { + if (ch.id == id) { + removeChannel(ch); + return; + } + } + } + + public void updateChannel(Channel ch) { + if (!loading) { + broadcastMessage(ACTION_CHANNEL_UPDATE, ch); + } + } + + public void addProgramme(Programme p) { + if (!loading) { + broadcastMessage(ACTION_PROGRAMME_ADD, p); + } + } + + public void removeProgramme(Programme p) { + if (!loading) { + broadcastMessage(ACTION_PROGRAMME_DELETE, p); + } + } + + public void updateProgramme(Programme p) { + if (!loading) { + broadcastMessage(ACTION_PROGRAMME_UPDATE, p); + } + } + + public void addRecording(Recording rec) { + recordings.add(rec); + + if (!loading) { + broadcastMessage(ACTION_DVR_ADD, rec); + } + } + + public List getRecordings() { + return recordings; + } + + public void removeRecording(Recording rec) { + recordings.remove(rec); + + if (!loading) { + broadcastMessage(ACTION_DVR_DELETE, rec); + } + } + + public Recording getRecording(long id) { + for (Recording rec : getRecordings()) { + if (rec.id == id) { + return rec; + } + } + return null; + } + + public void removeRecording(long id) { + for (Recording rec : getRecordings()) { + if (rec.id == id) { + removeRecording(rec); + return; + } + } + } + + public void updateRecording(Recording rec) { + if (!loading) { + broadcastMessage(ACTION_DVR_UPDATE, rec); + } + } + + public void setLoading(boolean b) { + if (loading != b) { + broadcastMessage(ACTION_LOADING, b); + } + loading = b; + } + + public void clearAll() { + tags.clear(); + recordings.clear(); + + for (Channel ch : channels) { + ch.epg.clear(); + ch.recordings.clear(); + } + channels.clear(); + + for (Subscription s : subscriptions) { + s.streams.clear(); + } + subscriptions.clear(); + + ChannelTag tag = new ChannelTag(); + tag.id = 0; + tag.name = getString(R.string.pr_all_channels); + tags.add(tag); + } + + public void addSubscription(Subscription s) { + subscriptions.add(s); + + if (!loading) { + broadcastMessage(ACTION_SUBSCRIPTION_ADD, s); + } + } + + public List getSubscriptions() { + return subscriptions; + } + + public void removeSubscription(Subscription s) { + s.streams.clear(); + subscriptions.remove(s); + + if (!loading) { + broadcastMessage(ACTION_SUBSCRIPTION_DELETE, s); + } + } + + public Subscription getSubscription(long id) { + for (Subscription s : getSubscriptions()) { + if (s.id == id) { + return s; + } + } + return null; + } + + public void removeSubscription(long id) { + for (Subscription s : getSubscriptions()) { + if (s.id == id) { + removeSubscription(s); + return; + } + } + } + + public void updateSubscription(Subscription s) { + if (!loading) { + broadcastMessage(ACTION_SUBSCRIPTION_UPDATE, s); + } + } + + public void addTicket(HttpTicket t) { + broadcastMessage(ACTION_TICKET_ADD, t); + } + + public boolean isLoading() { + return loading; + } + + public static SparseArray getContentTypes(Resources resources) { SparseArray ret = new SparseArray(); - - String[] s = ctx.getResources().getStringArray(R.array.pr_content_type0); - for(int i=0; i responseHandelers; - private LinkedList messageQueue; - private boolean auth; - private Selector selector; - - public HTSConnection(HTSConnectionListener listener, String clientName, String clientVersion) { - running = false; - lock = new ReentrantLock(); - inBuf = ByteBuffer.allocateDirect(1024 * 1024); - inBuf.limit(4); - responseHandelers = new HashMap(); - messageQueue = new LinkedList(); - - this.listener = listener; - this.clientName = clientName; - this.clientVersion = clientVersion; - } - - public void setRunning(boolean b) { - try { - lock.lock(); - running = false; - } finally { - lock.unlock(); - } - } - - //sychronized, blocking connect - public void open(String hostname, int port) { - if (running) { - return; - } - - final Object signal = new Object(); - - lock.lock(); - try { - selector = Selector.open(); - socketChannel = SocketChannel.open(); - socketChannel.configureBlocking(false); - socketChannel.socket().setKeepAlive(true); - socketChannel.socket().setSoTimeout(5000); - socketChannel.register(selector, SelectionKey.OP_CONNECT, signal); - socketChannel.connect(new InetSocketAddress(hostname, port)); - - running = true; - start(); - } catch (Exception ex) { - Log.e(TAG, "Can't open connection", ex); - listener.onError(CONNECTION_REFUSED_ERROR); - return; - } finally { - lock.unlock(); - } - - synchronized (signal) { - try { - signal.wait(5000); - if (socketChannel.isConnectionPending()) { - listener.onError(TIMEOUT_ERROR); - close(); - } - } catch (InterruptedException ex) { - } - } - } - - public boolean isConnected() { - return socketChannel != null - && socketChannel.isOpen() - && socketChannel.isConnected() - && running; - } - - //sycnronized, blocking auth - public void authenticate(String username, final String password) { - if (auth || !running) { - return; - } - - auth = false; - final HTSMessage authMessage = new HTSMessage(); - authMessage.setMethod("enableAsyncMetadata"); - authMessage.putField("username", username); - final HTSResponseHandler authHandler = new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - auth = response.getInt("noaccess", 0) != 1; - if (!auth) { - listener.onError(HTS_AUTH_ERROR); - } - synchronized (authMessage) { - authMessage.notify(); - } - } - }; - - HTSMessage helloMessage = new HTSMessage(); - helloMessage.setMethod("hello"); - helloMessage.putField("clientname", this.clientName); - helloMessage.putField("clientversion", this.clientVersion); - helloMessage.putField("htspversion", HTSMessage.HTSP_VERSION); - helloMessage.putField("username", username); - sendMessage(helloMessage, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - - protocolVersion = response.getInt("htspversion"); - MessageDigest md; - try { - md = MessageDigest.getInstance("SHA1"); - md.update(password.getBytes()); - md.update(response.getByteArray("challenge")); - authMessage.putField("digest", md.digest()); - sendMessage(authMessage, authHandler); - } catch (NoSuchAlgorithmException ex) { - return; - } - } - }); - - synchronized (authMessage) { - try { - authMessage.wait(5000); - if (!auth) { - listener.onError(TIMEOUT_ERROR); - } - return; - } catch (InterruptedException ex) { - return; - } - } - } - - public boolean isAuthenticated() { - return auth; - } - - public void sendMessage(HTSMessage message, HTSResponseHandler listener) { - if (!isConnected()) { - return; - } - - lock.lock(); - try { - seq++; - message.putField("seq", seq); - responseHandelers.put(seq, listener); - socketChannel.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ | SelectionKey.OP_CONNECT); - messageQueue.add(message); - selector.wakeup(); - } catch (Exception ex) { - Log.e(TAG, "Can't transmit message", ex); - this.listener.onError(ex); - } finally { - lock.unlock(); - } - } - - public void close() { - lock.lock(); - try { - responseHandelers.clear(); - messageQueue.clear(); - auth = false; - running = false; - socketChannel.register(selector, 0); - socketChannel.close(); - } catch (Exception ex) { - Log.e(TAG, "Can't close connection", ex); - } finally { - lock.unlock(); - } - } - - @Override - public void run() { - while (running) { - try { - selector.select(5000); - } catch (IOException ex) { - listener.onError(ex); - running = false; - continue; - } - - lock.lock(); - - try { - Iterator it = selector.selectedKeys().iterator(); - while (it.hasNext()) { - SelectionKey selKey = (SelectionKey) it.next(); - it.remove(); - processTcpSelectionKey(selKey); - } - - int ops = SelectionKey.OP_READ; - if (!messageQueue.isEmpty()) { - ops |= SelectionKey.OP_WRITE; - } - socketChannel.register(selector, ops); - } catch (Exception ex) { - Log.e(TAG, "Can't read message", ex); - listener.onError(ex); - running = false; - } finally { - lock.unlock(); - } - } - - close(); - } - - private void processTcpSelectionKey(SelectionKey selKey) throws IOException { - if (selKey.isConnectable() && selKey.isValid()) { - SocketChannel sChannel = (SocketChannel) selKey.channel(); - sChannel.finishConnect(); - final Object signal = selKey.attachment(); - synchronized (signal) { - signal.notify(); - } - sChannel.register(selector, SelectionKey.OP_READ); - } - - if (selKey.isReadable() && selKey.isValid()) { - SocketChannel sChannel = (SocketChannel) selKey.channel(); - int len = sChannel.read(inBuf); - if (len < 0) { - throw new IOException("Server went down"); - } - - HTSMessage msg = HTSMessage.parse(inBuf); - if (msg != null) { - handleMessage(msg); - } - } - if (selKey.isWritable() && selKey.isValid()) { - SocketChannel sChannel = (SocketChannel) selKey.channel(); - HTSMessage msg = messageQueue.poll(); - if (msg != null) { - msg.transmit(sChannel); - } - } - } - - private void handleMessage(HTSMessage msg) { - if (msg.containsField("seq")) { - int respSeq = msg.getInt("seq"); - HTSResponseHandler handler = responseHandelers.get(respSeq); - responseHandelers.remove(respSeq); - - if (handler != null) { - handler.handleResponse(msg); - return; - } - } - - listener.onMessage(msg); - } - - public int getProtocolVersion() { - return this.protocolVersion; - } + public static final int TIMEOUT_ERROR = 1; + public static final int CONNECTION_REFUSED_ERROR = 2; + public static final int CONNECTION_LOST_ERROR = 3; + public static final int HTS_AUTH_ERROR = 4; + public static final int HTS_MESSAGE_ERROR = 5; + private static final String TAG = "HTSPConnection"; + private volatile boolean running; + private Lock lock; + private SocketChannel socketChannel; + private ByteBuffer inBuf; + private int seq; + private String clientName; + private String clientVersion; + private int protocolVersion; + + private HTSConnectionListener listener; + private SparseArray responseHandelers; + private LinkedList messageQueue; + private boolean auth; + private Selector selector; + + public HTSConnection(HTSConnectionListener listener, String clientName, + String clientVersion) { + running = false; + lock = new ReentrantLock(); + inBuf = ByteBuffer.allocateDirect(1024 * 1024); + inBuf.limit(4); + responseHandelers = new SparseArray(); + messageQueue = new LinkedList(); + + this.listener = listener; + this.clientName = clientName; + this.clientVersion = clientVersion; + } + + public void setRunning(boolean b) { + try { + lock.lock(); + running = false; + } finally { + lock.unlock(); + } + } + + // sychronized, blocking connect + public void open(String hostname, int port) { + if (running) { + return; + } + + final Object signal = new Object(); + + lock.lock(); + try { + selector = Selector.open(); + socketChannel = SocketChannel.open(); + socketChannel.configureBlocking(false); + socketChannel.socket().setKeepAlive(true); + socketChannel.socket().setSoTimeout(5000); + socketChannel.register(selector, SelectionKey.OP_CONNECT, signal); + socketChannel.connect(new InetSocketAddress(hostname, port)); + + running = true; + start(); + } catch (Exception ex) { + Log.e(TAG, "Can't open connection", ex); + listener.onError(CONNECTION_REFUSED_ERROR); + return; + } finally { + lock.unlock(); + } + + synchronized (signal) { + try { + signal.wait(5000); + if (socketChannel.isConnectionPending() || !running) { + listener.onError(TIMEOUT_ERROR); + close(); + } + } catch (InterruptedException ex) { + } + } + } + + public boolean isConnected() { + return socketChannel != null && socketChannel.isOpen() + && socketChannel.isConnected() && running; + } + + // sycnronized, blocking auth + public void authenticate(String username, final String password) { + if (auth || !running) { + return; + } + + auth = false; + final HTSMessage authMessage = new HTSMessage(); + authMessage.setMethod("enableAsyncMetadata"); + authMessage.putField("username", username); + final HTSResponseHandler authHandler = new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + auth = response.getInt("noaccess", 0) != 1; + if (!auth) { + listener.onError(HTS_AUTH_ERROR); + } + synchronized (authMessage) { + authMessage.notify(); + } + } + }; + + HTSMessage helloMessage = new HTSMessage(); + helloMessage.setMethod("hello"); + helloMessage.putField("clientname", this.clientName); + helloMessage.putField("clientversion", this.clientVersion); + helloMessage.putField("htspversion", HTSMessage.HTSP_VERSION); + helloMessage.putField("username", username); + sendMessage(helloMessage, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + + protocolVersion = response.getInt("htspversion"); + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA1"); + md.update(password.getBytes()); + md.update(response.getByteArray("challenge")); + authMessage.putField("digest", md.digest()); + sendMessage(authMessage, authHandler); + } catch (NoSuchAlgorithmException ex) { + return; + } + } + }); + + synchronized (authMessage) { + try { + authMessage.wait(5000); + if (!auth) { + listener.onError(TIMEOUT_ERROR); + } + return; + } catch (InterruptedException ex) { + return; + } + } + } + + public boolean isAuthenticated() { + return auth; + } + + public void sendMessage(HTSMessage message, HTSResponseHandler listener) { + if (!isConnected()) { + return; + } + + lock.lock(); + try { + seq++; + message.putField("seq", seq); + responseHandelers.put(seq, listener); + socketChannel.register(selector, SelectionKey.OP_WRITE + | SelectionKey.OP_READ | SelectionKey.OP_CONNECT); + messageQueue.add(message); + selector.wakeup(); + } catch (Exception ex) { + Log.e(TAG, "Can't transmit message", ex); + this.listener.onError(ex); + } finally { + lock.unlock(); + } + } + + public void close() { + lock.lock(); + try { + responseHandelers.clear(); + messageQueue.clear(); + auth = false; + running = false; + socketChannel.register(selector, 0); + socketChannel.close(); + } catch (Exception ex) { + Log.e(TAG, "Can't close connection", ex); + } finally { + lock.unlock(); + } + } + + @Override + public void run() { + while (running) { + try { + selector.select(5000); + } catch (IOException ex) { + listener.onError(ex); + running = false; + continue; + } + + lock.lock(); + + try { + Iterator it = selector.selectedKeys().iterator(); + while (it.hasNext()) { + SelectionKey selKey = it.next(); + it.remove(); + processTcpSelectionKey(selKey); + } + + int ops = SelectionKey.OP_READ; + if (!messageQueue.isEmpty()) { + ops |= SelectionKey.OP_WRITE; + } + socketChannel.register(selector, ops); + } catch (Exception ex) { + Log.e(TAG, "Can't read message", ex); + listener.onError(ex); + running = false; + } finally { + lock.unlock(); + } + } + + close(); + } + + private void processTcpSelectionKey(SelectionKey selKey) throws IOException { + if (selKey.isConnectable() && selKey.isValid()) { + SocketChannel sChannel = (SocketChannel) selKey.channel(); + sChannel.finishConnect(); + final Object signal = selKey.attachment(); + synchronized (signal) { + signal.notify(); + } + sChannel.register(selector, SelectionKey.OP_READ); + } + + if (selKey.isReadable() && selKey.isValid()) { + SocketChannel sChannel = (SocketChannel) selKey.channel(); + int len = sChannel.read(inBuf); + if (len < 0) { + throw new IOException("Server went down"); + } + + HTSMessage msg = HTSMessage.parse(inBuf); + if (msg != null) { + handleMessage(msg); + } + } + if (selKey.isWritable() && selKey.isValid()) { + SocketChannel sChannel = (SocketChannel) selKey.channel(); + HTSMessage msg = messageQueue.poll(); + if (msg != null) { + msg.transmit(sChannel); + } + } + } + + private void handleMessage(HTSMessage msg) { + if (msg.containsField("seq")) { + int respSeq = msg.getInt("seq"); + HTSResponseHandler handler = responseHandelers.get(respSeq); + responseHandelers.remove(respSeq); + + if (handler != null) { + handler.handleResponse(msg); + return; + } + } + + listener.onMessage(msg); + } + + public int getProtocolVersion() { + return this.protocolVersion; + } } diff --git a/src/org/tvheadend/tvhguide/htsp/HTSMessage.java b/src/org/tvheadend/tvhguide/htsp/HTSMessage.java index 13c1fb5..d2adea5 100644 --- a/src/org/tvheadend/tvhguide/htsp/HTSMessage.java +++ b/src/org/tvheadend/tvhguide/htsp/HTSMessage.java @@ -30,382 +30,389 @@ import java.util.Map; /** - * + * * @author john-tornblom */ public class HTSMessage extends HashMap { - public static final long HTSP_VERSION = 8; - private static final byte HMF_MAP = 1; - private static final byte HMF_S64 = 2; - private static final byte HMF_STR = 3; - private static final byte HMF_BIN = 4; - private static final byte HMF_LIST = 5; - private ByteBuffer buf; - - public void putField(String name, Object value) { - if (value != null) { - put(name, value); - } - } - - public void setMethod(String name) { - put("method", name); - } - - public String getMethod() { - return getString("method", ""); - } - - public boolean containsField(String name) { - return containsKey(name); - } - - public BigInteger getBigInteger(String name) { - return (BigInteger) get(name); - } - - public long getLong(String name) { - return getBigInteger(name).longValue(); - } - - public long getLong(String name, long std) { - if (!containsField(name)) { - return std; - } - return getLong(name); - } - - public int getInt(String name) { - return getBigInteger(name).intValue(); - } - - public int getInt(String name, int std) { - if (!containsField(name)) { - return std; - } - return getInt(name); - } - - public String getString(String name, String std) { - if (!containsField(name)) { - return std; - } - return getString(name); - } - - public String getString(String name) { - Object obj = get(name); - if (obj == null) { - return null; - } - return obj.toString(); - } - - public List getLongList(String name) { - ArrayList list = new ArrayList(); - - if (!containsField(name)) { - return list; - } - - for (Object obj : (List) get(name)) { - if (obj instanceof BigInteger) { - list.add(((BigInteger) obj).longValue()); - } - } - - return list; - } - - List getLongList(String name, List std) { - if (!containsField(name)) { - return std; - } - - return getLongList(name); - } - - public List getIntList(String name) { - ArrayList list = new ArrayList(); - - if (!containsField(name)) { - return list; - } - - for (Object obj : (List) get(name)) { - if (obj instanceof BigInteger) { - list.add(((BigInteger) obj).intValue()); - } - } - - return list; - } - - List getIntList(String name, List std) { - if (!containsField(name)) { - return std; - } - - return getIntList(name); - } - - public List getList(String name) { - return (List) get(name); - } - - public byte[] getByteArray(String name) { - return (byte[]) get(name); - } - - public Date getDate(String name) { - return new Date(getLong(name) * 1000); - } - - public boolean transmit(SocketChannel ch) throws IOException { - if (buf == null) { - byte[] data = serializeBinary(this); - int len = data.length; - buf = ByteBuffer.allocateDirect(len + 4); - - buf.put((byte) ((len >> 24) & 0xFF)); - buf.put((byte) ((len >> 16) & 0xFF)); - buf.put((byte) ((len >> 8) & 0xFF)); - buf.put((byte) ((len) & 0xFF)); - buf.put(data); - buf.flip(); - } - - if (ch.write(buf) < 0) { - throw new IOException("Server went down"); - } - - if (buf.hasRemaining()) { - return false; - } else { - buf.flip(); - return true; - } - } - - public static String getHexString(byte[] b) throws Exception { - String result = ""; - for (int i = 0; i < b.length; i++) { - result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1); - } - return result; - } - - private static byte[] toByteArray(BigInteger big) { - byte[] b = big.toByteArray(); - byte b1[] = new byte[b.length]; - - for (int i = 0; i < b.length; i++) { - b1[i] = b[b.length - 1 - i]; - } - - return b1; - } - - private static BigInteger toBigInteger(byte b[]) { - byte b1[] = new byte[b.length + 1]; - - for (int i = 0; i < b.length; i++) { - b1[i + 1] = b[b.length - 1 - i]; - } - - return new BigInteger(b1); - } - - private static long uIntToLong(byte b1, byte b2, byte b3, byte b4) { - long i = 0; - i <<= 8; - i ^= b1 & 0xFF; - i <<= 8; - i ^= b2 & 0xFF; - i <<= 8; - i ^= b3 & 0xFF; - i <<= 8; - i ^= b4 & 0xFF; - return i; - } - - public static HTSMessage parse(ByteBuffer buf) throws IOException { - long len; - - if (buf.position() < 4) { - return null; - } - - len = uIntToLong(buf.get(0), buf.get(1), buf.get(2), buf.get(3)); - - if (len + 4 > buf.capacity()) { - buf.clear(); - throw new IOException("Mesage is to long"); - } - - if (buf.limit() == 4) { - buf.limit((int) (4 + len)); - } - - //Message not yet fully read - if (buf.position() < len + 4) { - return null; - } - - buf.flip(); - buf.getInt(); //drops 4 bytes - HTSMessage msg = deserializeBinary(buf); - - buf.limit(4); - buf.position(0); - return msg; - } - - private static byte[] serializeBinary(String name, Object value) throws IOException { - byte[] bName = name.getBytes(); - byte[] bData = new byte[0]; - byte type; - - if (value instanceof String) { - type = HTSMessage.HMF_STR; - bData = ((String) value).getBytes(); - } else if (value instanceof BigInteger) { - type = HTSMessage.HMF_S64; - bData = toByteArray((BigInteger) value); - } else if (value instanceof Integer) { - type = HTSMessage.HMF_S64; - bData = toByteArray(BigInteger.valueOf((Integer) value)); - } else if (value instanceof Long) { - type = HTSMessage.HMF_S64; - bData = toByteArray(BigInteger.valueOf((Long) value)); - } else if (value instanceof byte[]) { - type = HTSMessage.HMF_BIN; - bData = (byte[]) value; - } else if (value instanceof Map) { - type = HTSMessage.HMF_MAP; - bData = serializeBinary((Map) value); - } else if (value instanceof Collection) { - type = HTSMessage.HMF_LIST; - bData = serializeBinary((Collection) value); - } else if (value == null) { - throw new IOException("HTSP doesn't support null values"); - } else { - throw new IOException("Unhandled class for " + name + ": " + value - + " (" + value.getClass().getSimpleName() + ")"); - } - - byte[] buf = new byte[1 + 1 + 4 + bName.length + bData.length]; - buf[0] = type; - buf[1] = (byte) (bName.length & 0xFF); - buf[2] = (byte) ((bData.length >> 24) & 0xFF); - buf[3] = (byte) ((bData.length >> 16) & 0xFF); - buf[4] = (byte) ((bData.length >> 8) & 0xFF); - buf[5] = (byte) ((bData.length) & 0xFF); - - System.arraycopy(bName, 0, buf, 6, bName.length); - System.arraycopy(bData, 0, buf, 6 + bName.length, bData.length); - - return buf; - } - - private static byte[] serializeBinary(Collection list) throws IOException { - ByteBuffer buf = ByteBuffer.allocate(Short.MAX_VALUE); - - for (Object value : list) { - byte[] sub = serializeBinary("", value); - buf.put(sub); - } - - byte[] bBuf = new byte[buf.position()]; - buf.flip(); - buf.get(bBuf); - - return bBuf; - } - - private static byte[] serializeBinary(Map map) throws IOException { - ByteBuffer buf = ByteBuffer.allocate(Short.MAX_VALUE); - - for (Object key : map.keySet()) { - Object value = map.get(key); - byte[] sub = serializeBinary(key.toString(), value); - buf.put(sub); - } - - byte[] bBuf = new byte[buf.position()]; - buf.flip(); - buf.get(bBuf); - - return bBuf; - } - - private static HTSMessage deserializeBinary(ByteBuffer buf) throws IOException { - byte type, namelen; - long datalen; - - HTSMessage msg = new HTSMessage(); - int cnt = 0; - - while (buf.hasRemaining()) { - type = buf.get(); - namelen = buf.get(); - datalen = uIntToLong(buf.get(), buf.get(), buf.get(), buf.get()); - - if (datalen > Integer.MAX_VALUE) { - throw new IOException("Would get precision losses ;("); - } - if (buf.limit() < namelen + datalen) { - throw new IOException("Buffer limit exceeded"); - } - - //Get the key for the map (the name) - String name = null; - if (namelen == 0) { - name = Integer.toString(cnt++); - } else { - byte[] bName = new byte[namelen]; - buf.get(bName); - name = new String(bName); - } - - //Get the actual content - Object obj = null; - byte[] bData = new byte[(int) datalen]; //Should be long? - buf.get(bData); - - switch (type) { - case HTSMessage.HMF_STR: { - obj = new String(bData); - break; - } - case HMF_BIN: { - obj = bData; - break; - } - case HMF_S64: { - obj = toBigInteger(bData); - break; - } - case HMF_MAP: { - ByteBuffer sub = ByteBuffer.allocateDirect((int) datalen); - sub.put(bData); - sub.flip(); - obj = deserializeBinary(sub); - break; - } - case HMF_LIST: { - ByteBuffer sub = ByteBuffer.allocateDirect((int) datalen); - sub.put(bData); - sub.flip(); - obj = new ArrayList(deserializeBinary(sub).values()); - break; - } - default: - throw new IOException("Unknown data type"); - } - msg.putField(name, obj); - } - return msg; - } + /** + * + */ + private static final long serialVersionUID = 2975403077219488636L; + public static final long HTSP_VERSION = 8; + private static final byte HMF_MAP = 1; + private static final byte HMF_S64 = 2; + private static final byte HMF_STR = 3; + private static final byte HMF_BIN = 4; + private static final byte HMF_LIST = 5; + private ByteBuffer buf; + + public void putField(String name, Object value) { + if (value != null) { + put(name, value); + } + } + + public void setMethod(String name) { + put("method", name); + } + + public String getMethod() { + return getString("method", ""); + } + + public boolean containsField(String name) { + return containsKey(name); + } + + public BigInteger getBigInteger(String name) { + return (BigInteger) get(name); + } + + public long getLong(String name) { + return getBigInteger(name).longValue(); + } + + public long getLong(String name, long std) { + if (!containsField(name)) { + return std; + } + return getLong(name); + } + + public int getInt(String name) { + return getBigInteger(name).intValue(); + } + + public int getInt(String name, int std) { + if (!containsField(name)) { + return std; + } + return getInt(name); + } + + public String getString(String name, String std) { + if (!containsField(name)) { + return std; + } + return getString(name); + } + + public String getString(String name) { + Object obj = get(name); + if (obj == null) { + return null; + } + return obj.toString(); + } + + public List getLongList(String name) { + ArrayList list = new ArrayList(); + + if (!containsField(name)) { + return list; + } + + for (Object obj : (List) get(name)) { + if (obj instanceof BigInteger) { + list.add(((BigInteger) obj).longValue()); + } + } + + return list; + } + + List getLongList(String name, List std) { + if (!containsField(name)) { + return std; + } + + return getLongList(name); + } + + public List getIntList(String name) { + ArrayList list = new ArrayList(); + + if (!containsField(name)) { + return list; + } + + for (Object obj : (List) get(name)) { + if (obj instanceof BigInteger) { + list.add(((BigInteger) obj).intValue()); + } + } + + return list; + } + + List getIntList(String name, List std) { + if (!containsField(name)) { + return std; + } + + return getIntList(name); + } + + public List getList(String name) { + return (List) get(name); + } + + public byte[] getByteArray(String name) { + return (byte[]) get(name); + } + + public Date getDate(String name) { + return new Date(getLong(name) * 1000); + } + + public boolean transmit(SocketChannel ch) throws IOException { + if (buf == null) { + byte[] data = serializeBinary(this); + int len = data.length; + buf = ByteBuffer.allocateDirect(len + 4); + + buf.put((byte) ((len >> 24) & 0xFF)); + buf.put((byte) ((len >> 16) & 0xFF)); + buf.put((byte) ((len >> 8) & 0xFF)); + buf.put((byte) ((len) & 0xFF)); + buf.put(data); + buf.flip(); + } + + if (ch.write(buf) < 0) { + throw new IOException("Server went down"); + } + + if (buf.hasRemaining()) { + return false; + } else { + buf.flip(); + return true; + } + } + + public static String getHexString(byte[] b) throws Exception { + String result = ""; + for (int i = 0; i < b.length; i++) { + result += Integer.toString((b[i] & 0xff) + 0x100, 16).substring(1); + } + return result; + } + + private static byte[] toByteArray(BigInteger big) { + byte[] b = big.toByteArray(); + byte b1[] = new byte[b.length]; + + for (int i = 0; i < b.length; i++) { + b1[i] = b[b.length - 1 - i]; + } + + return b1; + } + + private static BigInteger toBigInteger(byte b[]) { + byte b1[] = new byte[b.length + 1]; + + for (int i = 0; i < b.length; i++) { + b1[i + 1] = b[b.length - 1 - i]; + } + + return new BigInteger(b1); + } + + private static long uIntToLong(byte b1, byte b2, byte b3, byte b4) { + long i = 0; + i <<= 8; + i ^= b1 & 0xFF; + i <<= 8; + i ^= b2 & 0xFF; + i <<= 8; + i ^= b3 & 0xFF; + i <<= 8; + i ^= b4 & 0xFF; + return i; + } + + public static HTSMessage parse(ByteBuffer buf) throws IOException { + long len; + + if (buf.position() < 4) { + return null; + } + + len = uIntToLong(buf.get(0), buf.get(1), buf.get(2), buf.get(3)); + + if (len + 4 > buf.capacity()) { + buf.clear(); + throw new IOException("Mesage is to long"); + } + + if (buf.limit() == 4) { + buf.limit((int) (4 + len)); + } + + // Message not yet fully read + if (buf.position() < len + 4) { + return null; + } + + buf.flip(); + buf.getInt(); // drops 4 bytes + HTSMessage msg = deserializeBinary(buf); + + buf.limit(4); + buf.position(0); + return msg; + } + + private static byte[] serializeBinary(String name, Object value) + throws IOException { + byte[] bName = name.getBytes(); + byte[] bData = new byte[0]; + byte type; + + if (value instanceof String) { + type = HTSMessage.HMF_STR; + bData = ((String) value).getBytes(); + } else if (value instanceof BigInteger) { + type = HTSMessage.HMF_S64; + bData = toByteArray((BigInteger) value); + } else if (value instanceof Integer) { + type = HTSMessage.HMF_S64; + bData = toByteArray(BigInteger.valueOf((Integer) value)); + } else if (value instanceof Long) { + type = HTSMessage.HMF_S64; + bData = toByteArray(BigInteger.valueOf((Long) value)); + } else if (value instanceof byte[]) { + type = HTSMessage.HMF_BIN; + bData = (byte[]) value; + } else if (value instanceof Map) { + type = HTSMessage.HMF_MAP; + bData = serializeBinary((Map) value); + } else if (value instanceof Collection) { + type = HTSMessage.HMF_LIST; + bData = serializeBinary((Collection) value); + } else if (value == null) { + throw new IOException("HTSP doesn't support null values"); + } else { + throw new IOException("Unhandled class for " + name + ": " + value + + " (" + value.getClass().getSimpleName() + ")"); + } + + byte[] buf = new byte[1 + 1 + 4 + bName.length + bData.length]; + buf[0] = type; + buf[1] = (byte) (bName.length & 0xFF); + buf[2] = (byte) ((bData.length >> 24) & 0xFF); + buf[3] = (byte) ((bData.length >> 16) & 0xFF); + buf[4] = (byte) ((bData.length >> 8) & 0xFF); + buf[5] = (byte) ((bData.length) & 0xFF); + + System.arraycopy(bName, 0, buf, 6, bName.length); + System.arraycopy(bData, 0, buf, 6 + bName.length, bData.length); + + return buf; + } + + private static byte[] serializeBinary(Collection list) + throws IOException { + ByteBuffer buf = ByteBuffer.allocate(Short.MAX_VALUE); + + for (Object value : list) { + byte[] sub = serializeBinary("", value); + buf.put(sub); + } + + byte[] bBuf = new byte[buf.position()]; + buf.flip(); + buf.get(bBuf); + + return bBuf; + } + + private static byte[] serializeBinary(Map map) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(Short.MAX_VALUE); + + for (Object key : map.keySet()) { + Object value = map.get(key); + byte[] sub = serializeBinary(key.toString(), value); + buf.put(sub); + } + + byte[] bBuf = new byte[buf.position()]; + buf.flip(); + buf.get(bBuf); + + return bBuf; + } + + private static HTSMessage deserializeBinary(ByteBuffer buf) + throws IOException { + byte type, namelen; + long datalen; + + HTSMessage msg = new HTSMessage(); + int cnt = 0; + + while (buf.hasRemaining()) { + type = buf.get(); + namelen = buf.get(); + datalen = uIntToLong(buf.get(), buf.get(), buf.get(), buf.get()); + + if (datalen > Integer.MAX_VALUE) { + throw new IOException("Would get precision losses ;("); + } + if (buf.limit() < namelen + datalen) { + throw new IOException("Buffer limit exceeded"); + } + + // Get the key for the map (the name) + String name = null; + if (namelen == 0) { + name = Integer.toString(cnt++); + } else { + byte[] bName = new byte[namelen]; + buf.get(bName); + name = new String(bName); + } + + // Get the actual content + Object obj = null; + byte[] bData = new byte[(int) datalen]; // Should be long? + buf.get(bData); + + switch (type) { + case HTSMessage.HMF_STR: { + obj = new String(bData); + break; + } + case HMF_BIN: { + obj = bData; + break; + } + case HMF_S64: { + obj = toBigInteger(bData); + break; + } + case HMF_MAP: { + ByteBuffer sub = ByteBuffer.allocateDirect((int) datalen); + sub.put(bData); + sub.flip(); + obj = deserializeBinary(sub); + break; + } + case HMF_LIST: { + ByteBuffer sub = ByteBuffer.allocateDirect((int) datalen); + sub.put(bData); + sub.flip(); + obj = new ArrayList(deserializeBinary(sub).values()); + break; + } + default: + throw new IOException("Unknown data type"); + } + msg.putField(name, obj); + } + return msg; + } } diff --git a/src/org/tvheadend/tvhguide/htsp/HTSService.java b/src/org/tvheadend/tvhguide/htsp/HTSService.java index 0b5ed1f..5c4360f 100644 --- a/src/org/tvheadend/tvhguide/htsp/HTSService.java +++ b/src/org/tvheadend/tvhguide/htsp/HTSService.java @@ -18,15 +18,6 @@ */ package org.tvheadend.tvhguide.htsp; -import android.app.Service; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager.NameNotFoundException; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.os.Binder; -import android.os.IBinder; -import android.util.Log; import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; @@ -42,6 +33,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; + import org.tvheadend.tvhguide.R; import org.tvheadend.tvhguide.TVHGuideApplication; import org.tvheadend.tvhguide.model.Channel; @@ -54,832 +46,852 @@ import org.tvheadend.tvhguide.model.Stream; import org.tvheadend.tvhguide.model.Subscription; +import android.app.Service; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Binder; +import android.os.IBinder; +import android.util.Log; + /** - * + * * @author john-tornblom */ public class HTSService extends Service implements HTSConnectionListener { - public static final String ACTION_CONNECT = "org.me.tvhguide.htsp.CONNECT"; - public static final String ACTION_DISCONNECT = "org.me.tvhguide.htsp.DISCONNECT"; - public static final String ACTION_EPG_QUERY = "org.me.tvhguide.htsp.EPG_QUERY"; - public static final String ACTION_GET_EVENT = "org.me.tvhguide.htsp.GET_EVENT"; - public static final String ACTION_GET_EVENTS = "org.me.tvhguide.htsp.GET_EVENTS"; - public static final String ACTION_DVR_ADD = "org.me.tvhguide.htsp.DVR_ADD"; - public static final String ACTION_DVR_DELETE = "org.me.tvhguide.htsp.DVR_DELETE"; - public static final String ACTION_DVR_CANCEL = "org.me.tvhguide.htsp.DVR_CANCEL"; - public static final String ACTION_SUBSCRIBE = "org.me.tvhguide.htsp.SUBSCRIBE"; - public static final String ACTION_UNSUBSCRIBE = "org.me.tvhguide.htsp.UNSUBSCRIBE"; - public static final String ACTION_FEEDBACK = "org.me.tvhguide.htsp.FEEDBACK"; - public static final String ACTION_GET_TICKET = "org.me.tvhguide.htsp.GET_TICKET"; - private static final String TAG = "HTSService"; - private ScheduledExecutorService execService; - private HTSConnection connection; - PackageInfo packInfo; - - public class LocalBinder extends Binder { - - HTSService getService() { - return HTSService.this; - } - } - - @Override - public void onCreate() { - execService = Executors.newScheduledThreadPool(5); - try { - packInfo = getPackageManager().getPackageInfo(getPackageName(), 0); - } catch (NameNotFoundException ex) { - Log.e(TAG, "Can't get package info", ex); - } - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (ACTION_CONNECT.equals(intent.getAction())) { - boolean force = intent.getBooleanExtra("force", false); - final String hostname = intent.getStringExtra("hostname"); - final int port = intent.getIntExtra("port", 9982); - final String username = intent.getStringExtra("username"); - final String password = intent.getStringExtra("password"); - - if (connection != null && force) { - connection.close(); - } - - if (connection == null || !connection.isConnected()) { - final TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.clearAll(); - app.setLoading(true); - connection = new HTSConnection(this, packInfo.packageName, packInfo.versionName); - - //Since this is blocking, spawn to a new thread - execService.execute(new Runnable() { - - public void run() { - connection.open(hostname, port); - connection.authenticate(username, password); - } - }); - } - } else if (connection == null || !connection.isConnected()) { - Log.e(TAG, "No connection to perform " + intent.getAction()); - } else if (ACTION_DISCONNECT.equals(intent.getAction())) { - connection.close(); - } else if (ACTION_GET_EVENT.equals(intent.getAction())) { - getEvent(intent.getLongExtra("eventId", 0)); - } else if (ACTION_GET_EVENTS.equals(intent.getAction())) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Channel ch = app.getChannel(intent.getLongExtra("channelId", 0)); - getEvents(ch, - intent.getLongExtra("eventId", 0), - intent.getIntExtra("count", 10)); - } else if (ACTION_DVR_ADD.equals(intent.getAction())) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Channel ch = app.getChannel(intent.getLongExtra("channelId", 0)); - addDvrEntry(ch, intent.getLongExtra("eventId", 0)); - } else if (ACTION_DVR_DELETE.equals(intent.getAction())) { - deleteDvrEntry(intent.getLongExtra("id", 0)); - } else if (ACTION_DVR_CANCEL.equals(intent.getAction())) { - cancelDvrEntry(intent.getLongExtra("id", 0)); - } else if (ACTION_EPG_QUERY.equals(intent.getAction())) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Channel ch = app.getChannel(intent.getLongExtra("channelId", 0)); - epgQuery(ch, - intent.getStringExtra("query"), - intent.getLongExtra("tagId", 0)); - } else if (ACTION_SUBSCRIBE.equals(intent.getAction())) { - subscribe(intent.getLongExtra("channelId", 0), - intent.getLongExtra("subscriptionId", 0), - intent.getIntExtra("maxWidth", 0), - intent.getIntExtra("maxHeight", 0), - intent.getStringExtra("audioCodec"), - intent.getStringExtra("videoCodec")); - } else if (ACTION_UNSUBSCRIBE.equals(intent.getAction())) { - unsubscribe(intent.getLongExtra("subscriptionId", 0)); - } else if (ACTION_FEEDBACK.equals(intent.getAction())) { - feedback(intent.getLongExtra("subscriptionId", 0), - intent.getIntExtra("speed", 0)); - } else if (ACTION_GET_TICKET.equals(intent.getAction())) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Channel ch = app.getChannel(intent.getLongExtra("channelId", 0)); - Recording rec = app.getRecording(intent.getLongExtra("dvrId", 0)); - if (ch != null) { - getTicket(ch); - } else if (rec != null) { - getTicket(rec); - } - } - - - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - execService.shutdown(); - if (connection != null) { - connection.close(); - } - } - - private void showError(final String error) { - if (error == null || error.length() < 0) { - return; - } - - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.setLoading(false); - app.broadcastError(error); - } - - private void showError(int recourceId) { - showError(getString(recourceId)); - } - - public void onError(int errorCode) { - switch (errorCode) { - case HTSConnection.CONNECTION_LOST_ERROR: - showError(R.string.err_con_lost); - break; - case HTSConnection.TIMEOUT_ERROR: - showError("Connection timeout"); - break; - case HTSConnection.CONNECTION_REFUSED_ERROR: - showError(R.string.err_connect); - break; - case HTSConnection.HTS_AUTH_ERROR: - showError(R.string.err_auth); - break; - } - } - - public void onError(Exception ex) { - showError(ex.getLocalizedMessage()); - } - - @Override - public IBinder onBind(Intent intent) { - return mBinder; - } - private final IBinder mBinder = new LocalBinder(); - - private void onTagAdd(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - ChannelTag tag = new ChannelTag(); - tag.id = msg.getLong("tagId"); - tag.name = msg.getString("tagName", null); - tag.icon = msg.getString("tagIcon", null); - //tag.members = response.getIntList("members"); - app.addChannelTag(tag); - if (tag.icon != null) { - getChannelTagIcon(tag); - } - } - - private void onTagUpdate(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - ChannelTag tag = app.getChannelTag(msg.getLong("tagId")); - if (tag == null) { - return; - } - - tag.name = msg.getString("tagName", tag.name); - String icon = msg.getString("tagIcon", tag.icon); - if (icon == null) { - tag.icon = null; - tag.iconBitmap = null; - } else if (!icon.equals(tag.icon)) { - tag.icon = icon; - getChannelTagIcon(tag); - } - } - - private void onTagDelete(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.removeChannelTag(msg.getLong("tagId")); - } - - private void onChannelAdd(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - final Channel ch = new Channel(); - ch.id = msg.getLong("channelId"); - ch.name = msg.getString("channelName", null); - ch.number = msg.getInt("channelNumber", 0); - ch.icon = msg.getString("channelIcon", null); - ch.tags = msg.getIntList("tags", ch.tags); - - if (ch.number == 0) { - ch.number = (int) (ch.id + 25000); - } - - app.addChannel(ch); - if (ch.icon != null) { - getChannelIcon(ch); - } - long currEventId = msg.getLong("eventId", 0); - long nextEventId = msg.getLong("nextEventId", 0); - - ch.isTransmitting = currEventId != 0; - - if (currEventId > 0) { - getEvents(ch, currEventId, 5); - } else if (nextEventId > 0) { - getEvents(ch, nextEventId, 5); - } - } - - private void onChannelUpdate(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - - final Channel ch = app.getChannel(msg.getLong("channelId")); - if (ch == null) { - return; - } - - ch.name = msg.getString("channelName", ch.name); - ch.number = msg.getInt("channelNumber", ch.number); - String icon = msg.getString("channelIcon", ch.icon); - ch.tags = msg.getIntList("tags", ch.tags); - - if (icon == null) { - ch.icon = null; - ch.iconBitmap = null; - } else if (!icon.equals(ch.icon)) { - ch.icon = icon; - getChannelIcon(ch); - } - //Remove programmes that have ended - long currEventId = msg.getLong("eventId", 0); - long nextEventId = msg.getLong("nextEventId", 0); - - ch.isTransmitting = currEventId != 0; - - Iterator it = ch.epg.iterator(); - ArrayList tmp = new ArrayList(); - - while (it.hasNext() && currEventId > 0) { - Programme p = it.next(); - if (p.id != currEventId) { - tmp.add(p); - } else { - break; - } - } - ch.epg.removeAll(tmp); - - for (Programme p : tmp) { - app.removeProgramme(p); - } - - final long eventId = currEventId != 0 ? currEventId : nextEventId; - if (eventId > 0 && ch.epg.size() < 2) { - execService.schedule(new Runnable() { - - public void run() { - getEvents(ch, eventId, 5); - } - }, 30, TimeUnit.SECONDS); - } else { - app.updateChannel(ch); - } - } - - private void onChannelDelete(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.removeChannel(msg.getLong("channelId")); - } - - private void onDvrEntryAdd(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Recording rec = new Recording(); - rec.id = msg.getLong("id"); - rec.description = msg.getString("description", ""); - rec.summary = msg.getString("summary", ""); - rec.error = msg.getString("error", null); - rec.start = msg.getDate("start"); - rec.state = msg.getString("state", null); - rec.stop = msg.getDate("stop"); - rec.title = msg.getString("title", null); - rec.channel = app.getChannel(msg.getLong("channel")); - if (rec.channel != null) { - rec.channel.recordings.add(rec); - } - app.addRecording(rec); - } - - private void onDvrEntryUpdate(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Recording rec = app.getRecording(msg.getLong("id")); - if (rec == null) { - return; - } - - rec.description = msg.getString("description", rec.description); - rec.summary = msg.getString("summary", rec.summary); - rec.error = msg.getString("error", rec.error); - rec.start = msg.getDate("start"); - rec.state = msg.getString("state", rec.state); - rec.stop = msg.getDate("stop"); - rec.title = msg.getString("title", rec.title); - app.updateRecording(rec); - } - - private void onDvrEntryDelete(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Recording rec = app.getRecording(msg.getLong("id")); - - if (rec == null || rec.channel == null) { - return; - } - - rec.channel.recordings.remove(rec); - for (Programme p : rec.channel.epg) { - if (p.recording == rec) { - p.recording = null; - app.updateProgramme(p); - break; - } - } - app.removeRecording(rec); - } - - private void onInitialSyncCompleted(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.setLoading(false); - } - - private void onStartSubscription(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Subscription subscription = app.getSubscription(msg.getLong("subscriptionId")); - if (subscription == null) { - return; - } - - for (Object obj : msg.getList("streams")) { - Stream s = new Stream(); - HTSMessage sub = (HTSMessage) obj; - - s.index = sub.getInt("index"); - s.type = sub.getString("type"); - s.language = sub.getString("language", ""); - s.width = sub.getInt("width", 0); - s.height = sub.getInt("height", 0); - - subscription.streams.add(s); - } - } - - private void onSubscriptionStatus(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Subscription s = app.getSubscription(msg.getLong("subscriptionId")); - if (s == null) { - return; - } - - String status = msg.getString("status", null); - if (s.status == null ? status != null : !s.status.equals(status)) { - s.status = status; - app.updateSubscription(s); - } - } - - private void onSubscriptionStop(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Subscription s = app.getSubscription(msg.getLong("subscriptionId")); - if (s == null) { - return; - } - - String status = msg.getString("status", null); - if (s.status == null ? status != null : !s.status.equals(status)) { - s.status = status; - app.updateSubscription(s); - } - app.removeSubscription(s); - } - - private void onMuxPacket(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Subscription sub = app.getSubscription(msg.getLong("subscriptionId")); - if (sub == null) { - return; - } - - Packet packet = new Packet(); - packet.dts = msg.getLong("dts", 0); - packet.pts = msg.getLong("pts", 0); - packet.duration = msg.getLong("duration"); - packet.frametype = msg.getInt("frametype"); - packet.payload = msg.getByteArray("payload"); - - for (Stream st : sub.streams) { - if (st.index == msg.getInt("stream")) { - packet.stream = st; - } - } - packet.subscription = sub; - app.broadcastPacket(packet); - } - - private void onQueueStatus(HTSMessage msg) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Subscription sub = app.getSubscription(msg.getLong("subscriptionId")); - if (sub == null) { - return; - } - if (msg.containsField("delay")) { - BigInteger delay = msg.getBigInteger("delay"); - delay = delay.divide(BigInteger.valueOf((1000))); - sub.delay = delay.longValue(); - } - sub.droppedBFrames = msg.getLong("Bdrops", sub.droppedBFrames); - sub.droppedIFrames = msg.getLong("Idrops", sub.droppedIFrames); - sub.droppedPFrames = msg.getLong("Pdrops", sub.droppedPFrames); - sub.packetCount = msg.getLong("packets", sub.packetCount); - sub.queSize = msg.getLong("bytes", sub.queSize); - - app.updateSubscription(sub); - } - - public void onMessage(HTSMessage msg) { - String method = msg.getMethod(); - if (method.equals("tagAdd")) { - onTagAdd(msg); - } else if (method.equals("tagUpdate")) { - onTagUpdate(msg); - } else if (method.equals("tagDelete")) { - onTagDelete(msg); - } else if (method.equals("channelAdd")) { - onChannelAdd(msg); - } else if (method.equals("channelUpdate")) { - onChannelUpdate(msg); - } else if (method.equals("channelDelete")) { - onChannelDelete(msg); - } else if (method.equals("initialSyncCompleted")) { - onInitialSyncCompleted(msg); - } else if (method.equals("dvrEntryAdd")) { - onDvrEntryAdd(msg); - } else if (method.equals("dvrEntryUpdate")) { - onDvrEntryUpdate(msg); - } else if (method.equals("dvrEntryDelete")) { - onDvrEntryDelete(msg); - } else if (method.equals("subscriptionStart")) { - onStartSubscription(msg); - } else if (method.equals("subscriptionStatus")) { - onSubscriptionStatus(msg); - } else if (method.equals("subscriptionStop")) { - onSubscriptionStop(msg); - } else if (method.equals("muxpkt")) { - onMuxPacket(msg); - } else if (method.equals("queueStatus")) { - onQueueStatus(msg); - } else { - Log.d(TAG, method.toString()); - } - } - - public String hashString(String s) { - try { - MessageDigest digest = java.security.MessageDigest.getInstance("MD5"); - digest.update(s.getBytes()); - byte messageDigest[] = digest.digest(); - - StringBuilder hexString = new StringBuilder(); - for (int i = 0; i < messageDigest.length; i++) { - hexString.append(Integer.toHexString(0xFF & messageDigest[i])); - } - return hexString.toString(); - - } catch (NoSuchAlgorithmException e) { - Log.e(TAG, "Can't create hash string", e); - } - - return ""; - } - - public void cacheImage(String url, File f) throws MalformedURLException, IOException { - Log.d(TAG, "Caching " + url + " as " + f.toString()); - - BufferedInputStream is = new BufferedInputStream(new URL(url).openStream()); - OutputStream os = new FileOutputStream(f); - - float scale = getResources().getDisplayMetrics().scaledDensity; - int width = (int) (64 * scale); - int height = (int) (64 * scale); - - BitmapFactory.Options o = new BitmapFactory.Options(); - o.outWidth = width; - o.outHeight = height; - - Bitmap bitmap = BitmapFactory.decodeStream(is, null, o); - - Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); - bitmap.recycle(); - - resizedBitmap.compress(Bitmap.CompressFormat.PNG, 100, os); - resizedBitmap.recycle(); - - os.close(); - is.close(); - } - - private Bitmap getIcon(final String url) throws MalformedURLException, IOException { - if (url == null || url.length() == 0) { - return null; - } - - File dir = getCacheDir(); - File f = new File(dir, hashString(url) + ".png"); - - if (!f.exists()) { - cacheImage(url, f); - } - - return BitmapFactory.decodeFile(f.toString()); - } - - private void getChannelIcon(final Channel ch) { - execService.execute(new Runnable() { - - public void run() { - - try { - ch.iconBitmap = getIcon(ch.icon); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.updateChannel(ch); - } catch (Throwable ex) { - Log.e(TAG, "Can't load channel icon", ex); - } - } - }); - } - - private void getChannelTagIcon(final ChannelTag tag) { - execService.execute(new Runnable() { - - public void run() { - - try { - tag.iconBitmap = getIcon(tag.icon); - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.updateChannelTag(tag); - } catch (Throwable ex) { - Log.e(TAG, "Can't load tag icon", ex); - } - } - }); - } - - private void getEvents(final Channel ch, final long eventId, int cnt) { - if (ch == null) { - return; - } - - HTSMessage request = new HTSMessage(); - request.setMethod("getEvents"); - request.putField("eventId", eventId); - request.putField("numFollowing", cnt); - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - - if (!response.containsKey("events")) { - return; - } - - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - - for (Object obj : response.getList("events")) { - Programme p = new Programme(); - HTSMessage sub = (HTSMessage) obj; - p.id = sub.getLong("eventId", 0); - p.nextId = sub.getLong("nextEventId", 0); - p.description = sub.getString("description", ""); - p.summary = sub.getString("summary", ""); - p.recording = app.getRecording(sub.getLong("dvrId", 0)); - p.contentType = sub.getInt("contentType", 0); - p.title = sub.getString("title"); - p.start = sub.getDate("start"); - p.stop = sub.getDate("stop"); - p.seriesInfo = buildSeriesInfo(sub); - p.starRating = sub.getInt("starRating", -1); - - p.channel = ch; - if (ch.epg.add(p)) { - app.addProgramme(p); - } - } - app.updateChannel(ch); - } - }); - } - - private void getEvent(long eventId) { - HTSMessage request = new HTSMessage(); - request.setMethod("getEvent"); - request.putField("eventId", eventId); - - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - Channel ch = app.getChannel(response.getLong("channelId")); - Programme p = new Programme(); - p.id = response.getLong("eventId"); - p.nextId = response.getLong("nextEventId", 0); - p.description = response.getString("description", ""); - p.summary = response.getString("summary", ""); - p.recording = app.getRecording(response.getLong("dvrId", 0)); - p.contentType = response.getInt("contentType", 0); - p.title = response.getString("title"); - p.start = response.getDate("start"); - p.stop = response.getDate("stop"); - p.seriesInfo = buildSeriesInfo(response); - p.starRating = response.getInt("starRating", -1); - - p.channel = ch; - - if (ch.epg.add(p)) { - app.addProgramme(p); - app.updateChannel(ch); - } - } - }); - } - - private SeriesInfo buildSeriesInfo(HTSMessage msg) { - SeriesInfo info = new SeriesInfo(); - - info.episodeCount = msg.getInt("episodeCount", 0); - info.episodeNumber = msg.getInt("episodeNumber", 0); - info.onScreen = msg.getString("onScreen", ""); - info.partCount = msg.getInt("partCount", 0); - info.partNumber = msg.getInt("partNumber", 0); - info.seasonCount = msg.getInt("seasonCount", 0); - info.seasonNumber = msg.getInt("seasonNumber", 0); - - return info; - } - - private void epgQuery(final Channel ch, String query, long tagId) { - HTSMessage request = new HTSMessage(); - request.setMethod("epgQuery"); - request.putField("query", query); - if (ch != null) { - request.putField("channelId", ch.id); - } - if (tagId > 0) { - request.putField("tagId", tagId); - } - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - - if (!response.containsKey("eventIds")) { - return; - } - - for (Long id : response.getLongList("eventIds")) { - getEvent(id); - } - } - }); - } - - private void cancelDvrEntry(long id) { - HTSMessage request = new HTSMessage(); - request.setMethod("cancelDvrEntry"); - request.putField("id", id); - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - - boolean success = response.getInt("success", 0) == 1; - } - }); - } - - private void deleteDvrEntry(long id) { - HTSMessage request = new HTSMessage(); - request.setMethod("deleteDvrEntry"); - request.putField("id", id); - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - - boolean success = response.getInt("success", 0) == 1; - } - }); - } - - private void addDvrEntry(final Channel ch, final long eventId) { - HTSMessage request = new HTSMessage(); - request.setMethod("addDvrEntry"); - request.putField("eventId", eventId); - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - if (response.getInt("success", 0) == 1) { - for (Programme p : ch.epg) { - if (p.id == eventId) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - p.recording = app.getRecording(response.getLong("id", 0)); - app.updateProgramme(p); - break; - } - } - } - String error = response.getString("error", null); - } - }); - } - - private void subscribe(long channelId, long subscriptionId, int maxWidth, int maxHeight, String aCodec, String vCodec) { - Subscription subscription = new Subscription(); - subscription.id = subscriptionId; - subscription.status = "Subscribing"; - - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.addSubscription(subscription); - - HTSMessage request = new HTSMessage(); - request.setMethod("subscribe"); - request.putField("channelId", channelId); - request.putField("maxWidth", maxWidth); - request.putField("maxHeight", maxHeight); - request.putField("audioCodec", aCodec); - request.putField("videoCodec", vCodec); - request.putField("subscriptionId", subscriptionId); - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - //NOP - } - }); - } - - private void unsubscribe(long subscriptionId) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.removeSubscription(subscriptionId); - - HTSMessage request = new HTSMessage(); - request.setMethod("unsubscribe"); - request.putField("subscriptionId", subscriptionId); - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - //NOP - } - }); - } - - private void feedback(long subscriptionId, int speed) { - HTSMessage request = new HTSMessage(); - request.setMethod("feedback"); - request.putField("subscriptionId", subscriptionId); - request.putField("speed", speed); - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - //NOP - } - }); - } - - private void getTicket(Channel ch) { - HTSMessage request = new HTSMessage(); - request.setMethod("getTicket"); - request.putField("channelId", ch.id); - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - String path = response.getString("path", null); - String ticket = response.getString("ticket", null); - - if (path != null && ticket != null) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.addTicket(new HttpTicket(path, ticket)); - } - } - }); - } - - private void getTicket(Recording rec) { - HTSMessage request = new HTSMessage(); - request.setMethod("getTicket"); - request.putField("dvrId", rec.id); - connection.sendMessage(request, new HTSResponseHandler() { - - public void handleResponse(HTSMessage response) { - String path = response.getString("path", null); - String ticket = response.getString("ticket", null); - - if (path != null && ticket != null) { - TVHGuideApplication app = (TVHGuideApplication) getApplication(); - app.addTicket(new HttpTicket(path, ticket)); - } - } - }); - } + public static final String ACTION_CONNECT = "org.me.tvhguide.htsp.CONNECT"; + public static final String ACTION_DISCONNECT = "org.me.tvhguide.htsp.DISCONNECT"; + public static final String ACTION_EPG_QUERY = "org.me.tvhguide.htsp.EPG_QUERY"; + public static final String ACTION_GET_EVENT = "org.me.tvhguide.htsp.GET_EVENT"; + public static final String ACTION_GET_EVENTS = "org.me.tvhguide.htsp.GET_EVENTS"; + public static final String ACTION_DVR_ADD = "org.me.tvhguide.htsp.DVR_ADD"; + public static final String ACTION_DVR_DELETE = "org.me.tvhguide.htsp.DVR_DELETE"; + public static final String ACTION_DVR_CANCEL = "org.me.tvhguide.htsp.DVR_CANCEL"; + public static final String ACTION_SUBSCRIBE = "org.me.tvhguide.htsp.SUBSCRIBE"; + public static final String ACTION_UNSUBSCRIBE = "org.me.tvhguide.htsp.UNSUBSCRIBE"; + public static final String ACTION_FEEDBACK = "org.me.tvhguide.htsp.FEEDBACK"; + public static final String ACTION_GET_TICKET = "org.me.tvhguide.htsp.GET_TICKET"; + private static final String TAG = "HTSService"; + private ScheduledExecutorService execService; + private HTSConnection connection; + PackageInfo packInfo; + + public static final int INITIAL_CHANNEL_LOADING = 5; + + public class LocalBinder extends Binder { + + HTSService getService() { + return HTSService.this; + } + } + + @Override + public void onCreate() { + execService = Executors.newScheduledThreadPool(5); + try { + packInfo = getPackageManager().getPackageInfo(getPackageName(), 0); + } catch (NameNotFoundException ex) { + Log.e(TAG, "Can't get package info", ex); + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (ACTION_CONNECT.equals(intent.getAction())) { + boolean force = intent.getBooleanExtra("force", false); + final String hostname = intent.getStringExtra("hostname"); + final int port = intent.getIntExtra("port", 9982); + final String username = intent.getStringExtra("username"); + final String password = intent.getStringExtra("password"); + + if (connection != null && force) { + connection.close(); + } + + if (connection == null || !connection.isConnected()) { + final TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.clearAll(); + app.setLoading(true); + connection = new HTSConnection(this, packInfo.packageName, + packInfo.versionName); + + // Since this is blocking, spawn to a new thread + execService.execute(new Runnable() { + + public void run() { + connection.open(hostname, port); + connection.authenticate(username, password); + } + }); + } + } else if (connection == null || !connection.isConnected()) { + Log.e(TAG, "No connection to perform " + intent.getAction()); + } else if (ACTION_DISCONNECT.equals(intent.getAction())) { + connection.close(); + } else if (ACTION_GET_EVENT.equals(intent.getAction())) { + getEvent(intent.getLongExtra("eventId", 0)); + } else if (ACTION_GET_EVENTS.equals(intent.getAction())) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + long channelId = intent.getLongExtra("channelId", 0); + int count = intent.getIntExtra("count", 10); + Channel ch = app.getChannel(channelId); + getEvents(ch, intent.getLongExtra("eventId", 0), count); + } else if (ACTION_DVR_ADD.equals(intent.getAction())) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Channel ch = app.getChannel(intent.getLongExtra("channelId", 0)); + addDvrEntry(ch, intent.getLongExtra("eventId", 0)); + } else if (ACTION_DVR_DELETE.equals(intent.getAction())) { + deleteDvrEntry(intent.getLongExtra("id", 0)); + } else if (ACTION_DVR_CANCEL.equals(intent.getAction())) { + cancelDvrEntry(intent.getLongExtra("id", 0)); + } else if (ACTION_EPG_QUERY.equals(intent.getAction())) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Channel ch = app.getChannel(intent.getLongExtra("channelId", 0)); + epgQuery(ch, intent.getStringExtra("query"), + intent.getLongExtra("tagId", 0)); + } else if (ACTION_SUBSCRIBE.equals(intent.getAction())) { + subscribe(intent.getLongExtra("channelId", 0), + intent.getLongExtra("subscriptionId", 0), + intent.getIntExtra("maxWidth", 0), + intent.getIntExtra("maxHeight", 0), + intent.getStringExtra("audioCodec"), + intent.getStringExtra("videoCodec")); + } else if (ACTION_UNSUBSCRIBE.equals(intent.getAction())) { + unsubscribe(intent.getLongExtra("subscriptionId", 0)); + } else if (ACTION_FEEDBACK.equals(intent.getAction())) { + feedback(intent.getLongExtra("subscriptionId", 0), + intent.getIntExtra("speed", 0)); + } else if (ACTION_GET_TICKET.equals(intent.getAction())) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Channel ch = app.getChannel(intent.getLongExtra("channelId", 0)); + Recording rec = app.getRecording(intent.getLongExtra("dvrId", 0)); + if (ch != null) { + getTicket(ch); + } else if (rec != null) { + getTicket(rec); + } + } + + return START_NOT_STICKY; + } + + @Override + public void onDestroy() { + execService.shutdown(); + if (connection != null) { + connection.close(); + } + } + + private void showError(final String error) { + if (error == null || error.length() < 0) { + return; + } + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.setLoading(false); + app.broadcastError(error); + } + + private void showError(int recourceId) { + showError(getString(recourceId)); + } + + public void onError(int errorCode) { + switch (errorCode) { + case HTSConnection.CONNECTION_LOST_ERROR: + showError(R.string.err_con_lost); + break; + case HTSConnection.TIMEOUT_ERROR: + showError("Connection timeout"); + break; + case HTSConnection.CONNECTION_REFUSED_ERROR: + showError(R.string.err_connect); + break; + case HTSConnection.HTS_AUTH_ERROR: + showError(R.string.err_auth); + break; + } + } + + public void onError(Exception ex) { + showError(ex.getLocalizedMessage()); + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + private final IBinder mBinder = new LocalBinder(); + + private void onTagAdd(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + ChannelTag tag = new ChannelTag(); + tag.id = msg.getLong("tagId"); + tag.name = msg.getString("tagName", null); + tag.icon = msg.getString("tagIcon", null); + // tag.members = response.getIntList("members"); + app.addChannelTag(tag); + if (tag.icon != null) { + getChannelTagIcon(tag); + } + } + + private void onTagUpdate(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + ChannelTag tag = app.getChannelTag(msg.getLong("tagId")); + if (tag == null) { + return; + } + + tag.name = msg.getString("tagName", tag.name); + String icon = msg.getString("tagIcon", tag.icon); + if (icon == null) { + tag.icon = null; + tag.iconBitmap = null; + } else if (!icon.equals(tag.icon)) { + tag.icon = icon; + getChannelTagIcon(tag); + } + } + + private void onTagDelete(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.removeChannelTag(msg.getLong("tagId")); + } + + private void onChannelAdd(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + final Channel ch = new Channel(); + ch.id = msg.getLong("channelId"); + ch.name = msg.getString("channelName", null); + ch.number = msg.getInt("channelNumber", 0); + ch.icon = msg.getString("channelIcon", null); + ch.tags = msg.getIntList("tags", ch.tags); + + if (ch.number == 0) { + ch.number = (int) (ch.id + 25000); + } + + app.addChannel(ch); + if (ch.icon != null) { + getChannelIcon(ch); + } + long currEventId = msg.getLong("eventId", 0); + long nextEventId = msg.getLong("nextEventId", 0); + + ch.isTransmitting = currEventId != 0; + + if (currEventId > 0) { + getEvents(ch, currEventId, INITIAL_CHANNEL_LOADING); + } else if (nextEventId > 0) { + getEvents(ch, nextEventId, INITIAL_CHANNEL_LOADING); + } + } + + private void onChannelUpdate(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + + final Channel ch = app.getChannel(msg.getLong("channelId")); + if (ch == null) { + return; + } + + ch.name = msg.getString("channelName", ch.name); + ch.number = msg.getInt("channelNumber", ch.number); + String icon = msg.getString("channelIcon", ch.icon); + ch.tags = msg.getIntList("tags", ch.tags); + + if (icon == null) { + ch.icon = null; + ch.iconBitmap = null; + } else if (!icon.equals(ch.icon)) { + ch.icon = icon; + getChannelIcon(ch); + } + // Remove programmes that have ended + long currEventId = msg.getLong("eventId", 0); + long nextEventId = msg.getLong("nextEventId", 0); + + ch.isTransmitting = currEventId != 0; + + Iterator it = ch.epg.iterator(); + ArrayList tmp = new ArrayList(); + + while (it.hasNext() && currEventId > 0) { + Programme p = it.next(); + if (p.id != currEventId) { + tmp.add(p); + } else { + break; + } + } + ch.epg.removeAll(tmp); + + for (Programme p : tmp) { + app.removeProgramme(p); + } + + final long eventId = currEventId != 0 ? currEventId : nextEventId; + if (eventId > 0 && ch.epg.size() < 2) { + execService.schedule(new Runnable() { + + public void run() { + getEvents(ch, eventId, INITIAL_CHANNEL_LOADING); + } + }, 30, TimeUnit.SECONDS); + } else { + app.updateChannel(ch); + } + } + + private void onChannelDelete(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.removeChannel(msg.getLong("channelId")); + } + + private void onDvrEntryAdd(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Recording rec = new Recording(); + rec.id = msg.getLong("id"); + rec.description = msg.getString("description", ""); + rec.summary = msg.getString("summary", ""); + rec.error = msg.getString("error", null); + rec.start = msg.getDate("start"); + rec.state = msg.getString("state", null); + rec.stop = msg.getDate("stop"); + rec.title = msg.getString("title", null); + rec.channel = app.getChannel(msg.getLong("channel")); + if (rec.channel != null) { + rec.channel.recordings.add(rec); + } + app.addRecording(rec); + } + + private void onDvrEntryUpdate(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Recording rec = app.getRecording(msg.getLong("id")); + if (rec == null) { + return; + } + + rec.description = msg.getString("description", rec.description); + rec.summary = msg.getString("summary", rec.summary); + rec.error = msg.getString("error", rec.error); + rec.start = msg.getDate("start"); + rec.state = msg.getString("state", rec.state); + rec.stop = msg.getDate("stop"); + rec.title = msg.getString("title", rec.title); + app.updateRecording(rec); + } + + private void onDvrEntryDelete(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Recording rec = app.getRecording(msg.getLong("id")); + + if (rec == null || rec.channel == null) { + return; + } + + rec.channel.recordings.remove(rec); + for (Programme p : rec.channel.epg) { + if (p.recording == rec) { + p.recording = null; + app.updateProgramme(p); + break; + } + } + app.removeRecording(rec); + } + + private void onInitialSyncCompleted(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.setLoading(false); + } + + private void onStartSubscription(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Subscription subscription = app.getSubscription(msg + .getLong("subscriptionId")); + if (subscription == null) { + return; + } + + for (Object obj : msg.getList("streams")) { + Stream s = new Stream(); + HTSMessage sub = (HTSMessage) obj; + + s.index = sub.getInt("index"); + s.type = sub.getString("type"); + s.language = sub.getString("language", ""); + s.width = sub.getInt("width", 0); + s.height = sub.getInt("height", 0); + + subscription.streams.add(s); + } + } + + private void onSubscriptionStatus(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Subscription s = app.getSubscription(msg.getLong("subscriptionId")); + if (s == null) { + return; + } + + String status = msg.getString("status", null); + if (s.status == null ? status != null : !s.status.equals(status)) { + s.status = status; + app.updateSubscription(s); + } + } + + private void onSubscriptionStop(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Subscription s = app.getSubscription(msg.getLong("subscriptionId")); + if (s == null) { + return; + } + + String status = msg.getString("status", null); + if (s.status == null ? status != null : !s.status.equals(status)) { + s.status = status; + app.updateSubscription(s); + } + app.removeSubscription(s); + } + + private void onMuxPacket(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Subscription sub = app.getSubscription(msg.getLong("subscriptionId")); + if (sub == null) { + return; + } + + Packet packet = new Packet(); + packet.dts = msg.getLong("dts", 0); + packet.pts = msg.getLong("pts", 0); + packet.duration = msg.getLong("duration"); + packet.frametype = msg.getInt("frametype"); + packet.payload = msg.getByteArray("payload"); + + for (Stream st : sub.streams) { + if (st.index == msg.getInt("stream")) { + packet.stream = st; + } + } + packet.subscription = sub; + app.broadcastPacket(packet); + } + + private void onQueueStatus(HTSMessage msg) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Subscription sub = app.getSubscription(msg.getLong("subscriptionId")); + if (sub == null) { + return; + } + if (msg.containsField("delay")) { + BigInteger delay = msg.getBigInteger("delay"); + delay = delay.divide(BigInteger.valueOf((1000))); + sub.delay = delay.longValue(); + } + sub.droppedBFrames = msg.getLong("Bdrops", sub.droppedBFrames); + sub.droppedIFrames = msg.getLong("Idrops", sub.droppedIFrames); + sub.droppedPFrames = msg.getLong("Pdrops", sub.droppedPFrames); + sub.packetCount = msg.getLong("packets", sub.packetCount); + sub.queSize = msg.getLong("bytes", sub.queSize); + + app.updateSubscription(sub); + } + + public void onMessage(HTSMessage msg) { + String method = msg.getMethod(); + if (method.equals("tagAdd")) { + onTagAdd(msg); + } else if (method.equals("tagUpdate")) { + onTagUpdate(msg); + } else if (method.equals("tagDelete")) { + onTagDelete(msg); + } else if (method.equals("channelAdd")) { + onChannelAdd(msg); + } else if (method.equals("channelUpdate")) { + onChannelUpdate(msg); + } else if (method.equals("channelDelete")) { + onChannelDelete(msg); + } else if (method.equals("initialSyncCompleted")) { + onInitialSyncCompleted(msg); + } else if (method.equals("dvrEntryAdd")) { + onDvrEntryAdd(msg); + } else if (method.equals("dvrEntryUpdate")) { + onDvrEntryUpdate(msg); + } else if (method.equals("dvrEntryDelete")) { + onDvrEntryDelete(msg); + } else if (method.equals("subscriptionStart")) { + onStartSubscription(msg); + } else if (method.equals("subscriptionStatus")) { + onSubscriptionStatus(msg); + } else if (method.equals("subscriptionStop")) { + onSubscriptionStop(msg); + } else if (method.equals("muxpkt")) { + onMuxPacket(msg); + } else if (method.equals("queueStatus")) { + onQueueStatus(msg); + } else { + Log.d(TAG, method.toString()); + } + } + + public String hashString(String s) { + try { + MessageDigest digest = java.security.MessageDigest + .getInstance("MD5"); + digest.update(s.getBytes()); + byte messageDigest[] = digest.digest(); + + StringBuilder hexString = new StringBuilder(); + for (int i = 0; i < messageDigest.length; i++) { + hexString.append(Integer.toHexString(0xFF & messageDigest[i])); + } + return hexString.toString(); + + } catch (NoSuchAlgorithmException e) { + Log.e(TAG, "Can't create hash string", e); + } + + return ""; + } + + public void cacheImage(String url, File f) throws MalformedURLException, + IOException { + Log.d(TAG, "Caching " + url + " as " + f.toString()); + + BufferedInputStream is = new BufferedInputStream( + new URL(url).openStream()); + OutputStream os = new FileOutputStream(f); + + float scale = getResources().getDisplayMetrics().scaledDensity; + int width = (int) (64 * scale); + int height = (int) (64 * scale); + + BitmapFactory.Options o = new BitmapFactory.Options(); + o.outWidth = width; + o.outHeight = height; + + Bitmap bitmap = BitmapFactory.decodeStream(is, null, o); + + Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, width, height, + true); + bitmap.recycle(); + + resizedBitmap.compress(Bitmap.CompressFormat.PNG, 100, os); + resizedBitmap.recycle(); + + os.close(); + is.close(); + } + + private Bitmap getIcon(final String url) throws MalformedURLException, + IOException { + if (url == null || url.length() == 0) { + return null; + } + + File dir = getCacheDir(); + File f = new File(dir, hashString(url) + ".png"); + + if (!f.exists()) { + cacheImage(url, f); + } + + return BitmapFactory.decodeFile(f.toString()); + } + + private void getChannelIcon(final Channel ch) { + execService.execute(new Runnable() { + + public void run() { + + try { + ch.iconBitmap = getIcon(ch.icon); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.updateChannel(ch); + } catch (Throwable ex) { + Log.e(TAG, "Can't load channel icon", ex); + } + } + }); + } + + private void getChannelTagIcon(final ChannelTag tag) { + execService.execute(new Runnable() { + + public void run() { + + try { + tag.iconBitmap = getIcon(tag.icon); + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.updateChannelTag(tag); + } catch (Throwable ex) { + Log.e(TAG, "Can't load tag icon", ex); + } + } + }); + } + + private void getEvents(final Channel ch, final long eventId, int cnt) { + if (ch == null) { + return; + } + + HTSMessage request = new HTSMessage(); + request.setMethod("getEvents"); + request.putField("eventId", eventId); + request.putField("numFollowing", cnt); + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + + if (!response.containsKey("events")) { + return; + } + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + + for (Object obj : response.getList("events")) { + Programme p = new Programme(); + HTSMessage sub = (HTSMessage) obj; + p.id = sub.getLong("eventId", 0); + p.nextId = sub.getLong("nextEventId", 0); + p.description = sub.getString("description", ""); + p.summary = sub.getString("summary", ""); + p.recording = app.getRecording(sub.getLong("dvrId", 0)); + p.contentType = sub.getInt("contentType", 0); + p.title = sub.getString("title"); + p.start = sub.getDate("start"); + p.stop = sub.getDate("stop"); + p.seriesInfo = buildSeriesInfo(sub); + p.starRating = sub.getInt("starRating", -1); + + p.channel = ch; + if (ch.epg.add(p)) { + app.addProgramme(p); + } + } + app.updateChannel(ch); + } + }); + } + + private void getEvent(long eventId) { + HTSMessage request = new HTSMessage(); + request.setMethod("getEvent"); + request.putField("eventId", eventId); + + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + Channel ch = app.getChannel(response.getLong("channelId")); + Programme p = new Programme(); + p.id = response.getLong("eventId"); + p.nextId = response.getLong("nextEventId", 0); + p.description = response.getString("description", ""); + p.summary = response.getString("summary", ""); + p.recording = app.getRecording(response.getLong("dvrId", 0)); + p.contentType = response.getInt("contentType", 0); + p.title = response.getString("title"); + p.start = response.getDate("start"); + p.stop = response.getDate("stop"); + p.seriesInfo = buildSeriesInfo(response); + p.starRating = response.getInt("starRating", -1); + + p.channel = ch; + + if (ch.epg.add(p)) { + app.addProgramme(p); + app.updateChannel(ch); + } + } + }); + } + + private SeriesInfo buildSeriesInfo(HTSMessage msg) { + SeriesInfo info = new SeriesInfo(); + + info.episodeCount = msg.getInt("episodeCount", 0); + info.episodeNumber = msg.getInt("episodeNumber", 0); + info.onScreen = msg.getString("onScreen", ""); + info.partCount = msg.getInt("partCount", 0); + info.partNumber = msg.getInt("partNumber", 0); + info.seasonCount = msg.getInt("seasonCount", 0); + info.seasonNumber = msg.getInt("seasonNumber", 0); + + return info; + } + + private void epgQuery(final Channel ch, String query, long tagId) { + HTSMessage request = new HTSMessage(); + request.setMethod("epgQuery"); + request.putField("query", query); + if (ch != null) { + request.putField("channelId", ch.id); + } + if (tagId > 0) { + request.putField("tagId", tagId); + } + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + + if (!response.containsKey("eventIds")) { + return; + } + + for (Long id : response.getLongList("eventIds")) { + getEvent(id); + } + } + }); + } + + private void cancelDvrEntry(long id) { + HTSMessage request = new HTSMessage(); + request.setMethod("cancelDvrEntry"); + request.putField("id", id); + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + + // boolean success = response.getInt("success", 0) == 1; + } + }); + } + + private void deleteDvrEntry(long id) { + HTSMessage request = new HTSMessage(); + request.setMethod("deleteDvrEntry"); + request.putField("id", id); + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + + // boolean success = response.getInt("success", 0) == 1; + } + }); + } + + private void addDvrEntry(final Channel ch, final long eventId) { + HTSMessage request = new HTSMessage(); + request.setMethod("addDvrEntry"); + request.putField("eventId", eventId); + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + if (response.getInt("success", 0) == 1) { + for (Programme p : ch.epg) { + if (p.id == eventId) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + p.recording = app.getRecording(response.getLong( + "id", 0)); + app.updateProgramme(p); + break; + } + } + } + // String error = response.getString("error", null); + } + }); + } + + private void subscribe(long channelId, long subscriptionId, int maxWidth, + int maxHeight, String aCodec, String vCodec) { + Subscription subscription = new Subscription(); + subscription.id = subscriptionId; + subscription.status = "Subscribing"; + + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.addSubscription(subscription); + + HTSMessage request = new HTSMessage(); + request.setMethod("subscribe"); + request.putField("channelId", channelId); + request.putField("maxWidth", maxWidth); + request.putField("maxHeight", maxHeight); + request.putField("audioCodec", aCodec); + request.putField("videoCodec", vCodec); + request.putField("subscriptionId", subscriptionId); + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + // NOP + } + }); + } + + private void unsubscribe(long subscriptionId) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.removeSubscription(subscriptionId); + + HTSMessage request = new HTSMessage(); + request.setMethod("unsubscribe"); + request.putField("subscriptionId", subscriptionId); + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + // NOP + } + }); + } + + private void feedback(long subscriptionId, int speed) { + HTSMessage request = new HTSMessage(); + request.setMethod("feedback"); + request.putField("subscriptionId", subscriptionId); + request.putField("speed", speed); + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + // NOP + } + }); + } + + private void getTicket(Channel ch) { + HTSMessage request = new HTSMessage(); + request.setMethod("getTicket"); + request.putField("channelId", ch.id); + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + String path = response.getString("path", null); + String ticket = response.getString("ticket", null); + + if (path != null && ticket != null) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.addTicket(new HttpTicket(path, ticket)); + } + } + }); + } + + private void getTicket(Recording rec) { + HTSMessage request = new HTSMessage(); + request.setMethod("getTicket"); + request.putField("dvrId", rec.id); + connection.sendMessage(request, new HTSResponseHandler() { + + public void handleResponse(HTSMessage response) { + String path = response.getString("path", null); + String ticket = response.getString("ticket", null); + + if (path != null && ticket != null) { + TVHGuideApplication app = (TVHGuideApplication) getApplication(); + app.addTicket(new HttpTicket(path, ticket)); + } + } + }); + } } diff --git a/src/org/tvheadend/tvhguide/htsp/SelectionThread.java b/src/org/tvheadend/tvhguide/htsp/SelectionThread.java index 64bdb67..b301277 100644 --- a/src/org/tvheadend/tvhguide/htsp/SelectionThread.java +++ b/src/org/tvheadend/tvhguide/htsp/SelectionThread.java @@ -18,7 +18,6 @@ */ package org.tvheadend.tvhguide.htsp; -import android.util.Log; import java.io.IOException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; @@ -31,247 +30,251 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import android.util.Log; + /** - * + * * @author john-tornblom */ public abstract class SelectionThread extends Thread { - private static final String TAG = "SelectionThread"; - private Selector selector; - private volatile boolean running; - private HashMap regBuf; - private Lock lock; + private static final String TAG = "SelectionThread"; + private Selector selector; + private volatile boolean running; + private HashMap regBuf; + private Lock lock; - public SelectionThread() { - running = false; - lock = new ReentrantLock(); - regBuf = new HashMap(); - } + public SelectionThread() { + running = false; + lock = new ReentrantLock(); + regBuf = new HashMap(); + } - public void setRunning(boolean b) { - try { - lock.lock(); - running = false; - } finally { - lock.unlock(); - } - } + public void setRunning(boolean b) { + try { + lock.lock(); + running = false; + } finally { + lock.unlock(); + } + } - void close(AbstractSelectableChannel channel) throws IOException { - lock.lock(); - try { - regBuf.remove(channel); - channel.close(); - } finally { - lock.unlock(); - } - } + void close(AbstractSelectableChannel channel) throws IOException { + lock.lock(); + try { + regBuf.remove(channel); + channel.close(); + } finally { + lock.unlock(); + } + } - public void register(AbstractSelectableChannel channel, int ops, boolean b) { - lock.lock(); - try { - int oldOps = 0; - if (regBuf.containsKey(channel)) { - oldOps = regBuf.get(channel); - } - if (b) { - ops |= oldOps; - } else { - ops = oldOps & ~ops; - } - regBuf.put(channel, ops); - if (selector != null) { - selector.wakeup(); - } - } finally { - lock.unlock(); - } - } + public void register(AbstractSelectableChannel channel, int ops, boolean b) { + lock.lock(); + try { + int oldOps = 0; + if (regBuf.containsKey(channel)) { + oldOps = regBuf.get(channel); + } + if (b) { + ops |= oldOps; + } else { + ops = oldOps & ~ops; + } + regBuf.put(channel, ops); + if (selector != null) { + selector.wakeup(); + } + } finally { + lock.unlock(); + } + } - @Override - public void run() { - try { - lock.lock(); - selector = Selector.open(); - running = true; - } catch (IOException ex) { - running = false; - Log.e(TAG, "Can't open a selector", ex); - } finally { - lock.unlock(); - } + @Override + public void run() { + try { + lock.lock(); + selector = Selector.open(); + running = true; + } catch (IOException ex) { + running = false; + Log.e(TAG, "Can't open a selector", ex); + } finally { + lock.unlock(); + } - while (running) { - select(5000); - } + while (running) { + select(5000); + } - try { - lock.lock(); - //Clean up - for (SelectionKey key : selector.keys()) { - try { - key.channel().close(); - } catch (IOException ex) { - Log.e(TAG, "Can't close channel", ex); - key.cancel(); - } + try { + lock.lock(); + // Clean up + for (SelectionKey key : selector.keys()) { + try { + key.channel().close(); + } catch (IOException ex) { + Log.e(TAG, "Can't close channel", ex); + key.cancel(); + } - } - try { - selector.close(); - } catch (IOException ex) { - Log.e(TAG, "Can't close selector", ex); - } - } finally { - lock.unlock(); - } - } + } + try { + selector.close(); + } catch (IOException ex) { + Log.e(TAG, "Can't close selector", ex); + } + } finally { + lock.unlock(); + } + } - private void select(int timeout) { - try { - selector.select(timeout); - } catch (IOException ex) { - Log.e(TAG, "Can't select socket", ex); - return; - } + private void select(int timeout) { + try { + selector.select(timeout); + } catch (IOException ex) { + Log.e(TAG, "Can't select socket", ex); + return; + } - Iterator it = selector.selectedKeys().iterator(); + Iterator it = selector.selectedKeys().iterator(); - //Process the selected keys - while (it.hasNext()) { - SelectionKey selKey = (SelectionKey) it.next(); - it.remove(); - processTcpSelectionKey(selKey); - } + // Process the selected keys + while (it.hasNext()) { + SelectionKey selKey = (SelectionKey) it.next(); + it.remove(); + processTcpSelectionKey(selKey); + } - try { - lock.lock(); - ArrayList tmp = new ArrayList(); - for (AbstractSelectableChannel ch : regBuf.keySet()) { - try { - int ops = regBuf.get(ch); - ch.register(selector, ops); - } catch (Throwable t) { - tmp.add(ch); - Log.e(TAG, "Can't register channel", t); - if (ch instanceof SocketChannel) { - onError((SocketChannel) ch); - } - } - } - for (AbstractSelectableChannel ch : tmp) { - regBuf.remove(ch); - } - } finally { - lock.unlock(); - } - } + try { + lock.lock(); + ArrayList tmp = new ArrayList(); + for (AbstractSelectableChannel ch : regBuf.keySet()) { + try { + int ops = regBuf.get(ch); + ch.register(selector, ops); + } catch (Throwable t) { + tmp.add(ch); + Log.e(TAG, "Can't register channel", t); + if (ch instanceof SocketChannel) { + onError((SocketChannel) ch); + } + } + } + for (AbstractSelectableChannel ch : tmp) { + regBuf.remove(ch); + } + } finally { + lock.unlock(); + } + } - private void processTcpSelectionKey(SelectionKey selKey) { - //Incomming connection established - if (selKey.isValid() && selKey.isAcceptable()) { - try { - ServerSocketChannel ssChannel = (ServerSocketChannel) selKey.channel(); - SocketChannel sChannel = ssChannel.accept(); - if (sChannel != null) { - sChannel.configureBlocking(false); - try { - onAccept(sChannel); - } catch (Throwable t) { - Log.e(TAG, "Can't establish connection", t); - onError(sChannel); - return; - } - } - } catch (Throwable t) { - Log.e(TAG, "Can't establish connection", t); - return; - } - } + private void processTcpSelectionKey(SelectionKey selKey) { + // Incomming connection established + if (selKey.isValid() && selKey.isAcceptable()) { + try { + ServerSocketChannel ssChannel = (ServerSocketChannel) selKey + .channel(); + SocketChannel sChannel = ssChannel.accept(); + if (sChannel != null) { + sChannel.configureBlocking(false); + try { + onAccept(sChannel); + } catch (Throwable t) { + Log.e(TAG, "Can't establish connection", t); + onError(sChannel); + return; + } + } + } catch (Throwable t) { + Log.e(TAG, "Can't establish connection", t); + return; + } + } - //Outgoing connection established - if (selKey.isValid() && selKey.isConnectable()) { - try { - SocketChannel sChannel = (SocketChannel) selKey.channel(); - if (!sChannel.finishConnect()) { - onError(sChannel); - selKey.cancel(); - return; - } - try { - onConnect(sChannel); - } catch (Throwable t) { - Log.e(TAG, "Can't establish connection", t); - onError(sChannel); - selKey.cancel(); - return; - } - } catch (Throwable t) { - Log.e(TAG, "Can't establish connection", t); - selKey.cancel(); - return; - } - } + // Outgoing connection established + if (selKey.isValid() && selKey.isConnectable()) { + try { + SocketChannel sChannel = (SocketChannel) selKey.channel(); + if (!sChannel.finishConnect()) { + onError(sChannel); + selKey.cancel(); + return; + } + try { + onConnect(sChannel); + } catch (Throwable t) { + Log.e(TAG, "Can't establish connection", t); + onError(sChannel); + selKey.cancel(); + return; + } + } catch (Throwable t) { + Log.e(TAG, "Can't establish connection", t); + selKey.cancel(); + return; + } + } - //Incomming data - if (selKey.isValid() && selKey.isReadable()) { - SocketChannel sChannel = (SocketChannel) selKey.channel(); - try { - onReadable(sChannel); - } catch (Throwable t) { - Log.e(TAG, "Can't read message", t); - onError(sChannel); - selKey.cancel(); - return; - } - } + // Incomming data + if (selKey.isValid() && selKey.isReadable()) { + SocketChannel sChannel = (SocketChannel) selKey.channel(); + try { + onReadable(sChannel); + } catch (Throwable t) { + Log.e(TAG, "Can't read message", t); + onError(sChannel); + selKey.cancel(); + return; + } + } - //Clear to send - if (selKey.isValid() && selKey.isWritable()) { - SocketChannel sChannel = (SocketChannel) selKey.channel(); - try { - onWrtiable(sChannel); - } catch (Throwable t) { - Log.e(TAG, "Can't send message", t); - onError(sChannel); - selKey.cancel(); - return; - } - } - } + // Clear to send + if (selKey.isValid() && selKey.isWritable()) { + SocketChannel sChannel = (SocketChannel) selKey.channel(); + try { + onWrtiable(sChannel); + } catch (Throwable t) { + Log.e(TAG, "Can't send message", t); + onError(sChannel); + selKey.cancel(); + return; + } + } + } - private void onAccept(SocketChannel ch) throws Exception { - onEvent(SelectionKey.OP_ACCEPT, ch); - } + private void onAccept(SocketChannel ch) throws Exception { + onEvent(SelectionKey.OP_ACCEPT, ch); + } - private void onConnect(SocketChannel ch) throws Exception { - onEvent(SelectionKey.OP_CONNECT, ch); - } + private void onConnect(SocketChannel ch) throws Exception { + onEvent(SelectionKey.OP_CONNECT, ch); + } - private void onReadable(SocketChannel ch) throws Exception { - onEvent(SelectionKey.OP_READ, ch); - } + private void onReadable(SocketChannel ch) throws Exception { + onEvent(SelectionKey.OP_READ, ch); + } - private void onError(SocketChannel ch) { - try { - lock.lock(); - ch.close(); - regBuf.remove(ch); - } catch (Exception ex) { - Log.e(TAG, null, ex); - } finally { - lock.unlock(); - } - try { - onEvent(-1, ch); - } catch (Exception ex) { - } - } + private void onError(SocketChannel ch) { + try { + lock.lock(); + ch.close(); + regBuf.remove(ch); + } catch (Exception ex) { + Log.e(TAG, null, ex); + } finally { + lock.unlock(); + } + try { + onEvent(-1, ch); + } catch (Exception ex) { + } + } - private void onWrtiable(SocketChannel ch) throws Exception { - onEvent(SelectionKey.OP_WRITE, ch); - } + private void onWrtiable(SocketChannel ch) throws Exception { + onEvent(SelectionKey.OP_WRITE, ch); + } - public abstract void onEvent(int selectionKey, SocketChannel ch) throws Exception; + public abstract void onEvent(int selectionKey, SocketChannel ch) + throws Exception; } diff --git a/src/org/tvheadend/tvhguide/intent/SearchIMDbIntent.java b/src/org/tvheadend/tvhguide/intent/SearchIMDbIntent.java index 63ebdb8..4ef810c 100644 --- a/src/org/tvheadend/tvhguide/intent/SearchIMDbIntent.java +++ b/src/org/tvheadend/tvhguide/intent/SearchIMDbIntent.java @@ -4,26 +4,29 @@ */ package org.tvheadend.tvhguide.intent; +import java.net.URLEncoder; + import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; -import java.net.URLEncoder; /** - * + * * @author john-tornblom */ public class SearchIMDbIntent extends Intent { - public SearchIMDbIntent(Context ctx, String query) { - super(Intent.ACTION_VIEW); + public SearchIMDbIntent(Context ctx, String query) { + super(Intent.ACTION_VIEW); - setData(Uri.parse("imdb:///find?s=tt&q=" + URLEncoder.encode(query))); + setData(Uri.parse("imdb:///find?s=tt&q=" + URLEncoder.encode(query))); - PackageManager packageManager = ctx.getPackageManager(); - if (packageManager.queryIntentActivities(this, PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) { - setData(Uri.parse("http://akas.imdb.org/find?s=tt&q=" + URLEncoder.encode(query))); - } - } + PackageManager packageManager = ctx.getPackageManager(); + if (packageManager.queryIntentActivities(this, + PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) { + setData(Uri.parse("http://akas.imdb.org/find?s=tt&q=" + + URLEncoder.encode(query))); + } + } } diff --git a/src/org/tvheadend/tvhguide/model/Channel.java b/src/org/tvheadend/tvhguide/model/Channel.java index fab9f66..4135f58 100644 --- a/src/org/tvheadend/tvhguide/model/Channel.java +++ b/src/org/tvheadend/tvhguide/model/Channel.java @@ -18,51 +18,54 @@ */ package org.tvheadend.tvhguide.model; -import android.graphics.Bitmap; import java.util.Collections; import java.util.List; -import java.util.Set; +import java.util.SortedSet; import java.util.TreeSet; +import android.graphics.Bitmap; + /** - * + * * @author john-tornblom */ public class Channel implements Comparable { - public long id; - public String name; - public String icon; - public int number; - public Set epg = Collections.synchronizedSortedSet(new TreeSet()); - public Set recordings = Collections.synchronizedSortedSet(new TreeSet()); - public List tags; - public Bitmap iconBitmap; - public boolean isTransmitting; - - public int compareTo(Channel that) { - return this.number - that.number; - } + public long id; + public String name; + public String icon; + public int number; + public SortedSet epg = Collections + .synchronizedSortedSet(new TreeSet()); + public SortedSet recordings = Collections + .synchronizedSortedSet(new TreeSet()); + public List tags; + public Bitmap iconBitmap; + public boolean isTransmitting; + + public int compareTo(Channel that) { + return this.number - that.number; + } - public boolean hasTag(long id) { - if (id == 0) { - return true; - } + public boolean hasTag(long id) { + if (id == 0) { + return true; + } - for (Integer i : tags) { - if (i == id) { - return true; - } - } - return false; - } + for (Integer i : tags) { + if (i == id) { + return true; + } + } + return false; + } - public boolean isRecording() { - for (Recording rec : recordings) { - if ("recording".equals(rec.state)) { - return true; - } - } - return false; - } + public boolean isRecording() { + for (Recording rec : recordings) { + if ("recording".equals(rec.state)) { + return true; + } + } + return false; + } } diff --git a/src/org/tvheadend/tvhguide/model/SeriesInfo.java b/src/org/tvheadend/tvhguide/model/SeriesInfo.java index 73277db..c587c80 100644 --- a/src/org/tvheadend/tvhguide/model/SeriesInfo.java +++ b/src/org/tvheadend/tvhguide/model/SeriesInfo.java @@ -1,5 +1,7 @@ package org.tvheadend.tvhguide.model; +import java.util.Locale; + public class SeriesInfo { public int seasonNumber; public int seasonCount; @@ -34,10 +36,11 @@ public String toString() { s += String.format("part %d", partNumber); } - if(s.length() > 0) { - s = s.substring(0,1).toUpperCase() + s.substring(1); + if (s.length() > 0) { + s = s.substring(0, 1).toUpperCase(Locale.getDefault()) + + s.substring(1); } - + return s; } } diff --git a/src/org/tvheadend/tvhguide/ui/HorizontalListView.java b/src/org/tvheadend/tvhguide/ui/HorizontalListView.java new file mode 100644 index 0000000..777a37d --- /dev/null +++ b/src/org/tvheadend/tvhguide/ui/HorizontalListView.java @@ -0,0 +1,426 @@ +package org.tvheadend.tvhguide.ui; + +import java.util.LinkedList; +import java.util.Queue; + +import android.content.Context; +import android.database.DataSetObserver; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.GestureDetector; +import android.view.GestureDetector.OnGestureListener; +import android.view.MotionEvent; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListAdapter; +import android.widget.Scroller; + +public class HorizontalListView extends AdapterView { + + public boolean mAlwaysOverrideTouch = true; + protected ListAdapter mAdapter; + private int mLeftViewIndex = -1; + private int mRightViewIndex = 0; + protected int mCurrentX; + protected int mNextX; + private int mMaxX = Integer.MAX_VALUE; + private int mDisplayOffset = 0; + protected Scroller mScroller; + private GestureDetector mGesture; + private Queue mRemovedViewQueue = new LinkedList(); + private OnItemSelectedListener mOnItemSelected; + private OnItemClickListener mOnItemClicked; + private OnItemLongClickListener mOnItemLongClicked; + private boolean mDataChanged = false; + private int selectedIndex; + private Runnable mRequestLayout; + + public HorizontalListView(Context context, AttributeSet attrs) { + super(context, attrs); + mRequestLayout = new Runnable() { + @Override + public void run() { + requestLayout(); + } + }; + initView(); + } + + private synchronized void initView() { + mLeftViewIndex = -1; + mRightViewIndex = 0; + mDisplayOffset = 0; + mCurrentX = 0; + mNextX = 0; + mMaxX = Integer.MAX_VALUE; + mScroller = new Scroller(getContext()); + mGesture = new GestureDetector(getContext(), mOnGesture); + } + + @Override + public void setOnItemSelectedListener( + AdapterView.OnItemSelectedListener listener) { + mOnItemSelected = listener; + } + + @Override + public void setOnItemClickListener(AdapterView.OnItemClickListener listener) { + mOnItemClicked = listener; + } + + @Override + protected ContextMenuInfo getContextMenuInfo() { + View child = getChildAt(selectedIndex); + return new AdapterView.AdapterContextMenuInfo(child, mLeftViewIndex + 1 + + selectedIndex, mAdapter.getItemId(mLeftViewIndex + 1 + + selectedIndex)); + } + + @Override + public void setOnItemLongClickListener( + AdapterView.OnItemLongClickListener listener) { + mOnItemLongClicked = listener; + } + + private DataSetObserver mDataObserver = new DataSetObserver() { + + @Override + public void onChanged() { + synchronized (HorizontalListView.this) { + mDataChanged = true; + } + invalidate(); + requestLayout(); + } + + @Override + public void onInvalidated() { + reset(); + invalidate(); + requestLayout(); + } + + }; + private boolean m_scrolling; + + @Override + public ListAdapter getAdapter() { + return mAdapter; + } + + @Override + public View getSelectedView() { + // TODO: implement + return null; + } + + @Override + public void createContextMenu(ContextMenu menu) { + if (!m_scrolling) { + super.createContextMenu(menu); + } + } + + @Override + public void setAdapter(ListAdapter adapter) { + if (mAdapter != null) { + mAdapter.unregisterDataSetObserver(mDataObserver); + } + mAdapter = adapter; + mAdapter.registerDataSetObserver(mDataObserver); + reset(); + } + + private synchronized void reset() { + initView(); + removeAllViewsInLayout(); + requestLayout(); + } + + @Override + public void setSelection(int position) { + // TODO: implement + } + + private void addAndMeasureChild(final View child, int viewPos) { + LayoutParams params = child.getLayoutParams(); + if (params == null) { + params = new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT); + } + + addViewInLayout(child, viewPos, params, true); + child.measure( + MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); + } + + @Override + protected synchronized void onLayout(boolean changed, int left, int top, + int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + + if (mAdapter == null) { + return; + } + + if (mDataChanged) { + int oldCurrentX = mCurrentX; + initView(); + removeAllViewsInLayout(); + mNextX = oldCurrentX; + mDataChanged = false; + } + + if (mScroller.computeScrollOffset()) { + int scrollx = mScroller.getCurrX(); + mNextX = scrollx; + } + + if (mNextX <= 0) { + mNextX = 0; + mScroller.forceFinished(true); + } + if (mNextX >= mMaxX) { + mNextX = mMaxX; + mScroller.forceFinished(true); + } + + int dx = mCurrentX - mNextX; + + removeNonVisibleItems(dx); + fillList(dx); + positionItems(dx); + + mCurrentX = mNextX; + + if (!mScroller.isFinished()) { + post(mRequestLayout); + + } + } + + private void fillList(final int dx) { + int edge = 0; + View child = getChildAt(getChildCount() - 1); + if (child != null) { + edge = child.getRight(); + } + fillListRight(edge, dx); + + edge = 0; + child = getChildAt(0); + if (child != null) { + edge = child.getLeft(); + } + fillListLeft(edge, dx); + + } + + private void fillListRight(int rightEdge, final int dx) { + while (rightEdge + dx < getWidth() + && mRightViewIndex < mAdapter.getCount()) { + + View child = mAdapter.getView(mRightViewIndex, + mRemovedViewQueue.poll(), this); + addAndMeasureChild(child, -1); + rightEdge += child.getMeasuredWidth(); + + if (mRightViewIndex == mAdapter.getCount() - 1) { + mMaxX = mCurrentX + rightEdge - getWidth(); + } + + if (mMaxX < 0) { + mMaxX = 0; + } + mRightViewIndex++; + } + + } + + private void fillListLeft(int leftEdge, final int dx) { + while (leftEdge + dx > 0 && mLeftViewIndex >= 0) { + View child = mAdapter.getView(mLeftViewIndex, + mRemovedViewQueue.poll(), this); + addAndMeasureChild(child, 0); + leftEdge -= child.getMeasuredWidth(); + mLeftViewIndex--; + mDisplayOffset -= child.getMeasuredWidth(); + } + } + + private void removeNonVisibleItems(final int dx) { + View child = getChildAt(0); + while (child != null && child.getRight() + dx <= 0) { + mDisplayOffset += child.getMeasuredWidth(); + mRemovedViewQueue.offer(child); + removeViewInLayout(child); + mLeftViewIndex++; + child = getChildAt(0); + + } + + child = getChildAt(getChildCount() - 1); + while (child != null && child.getLeft() + dx >= getWidth()) { + mRemovedViewQueue.offer(child); + removeViewInLayout(child); + mRightViewIndex--; + child = getChildAt(getChildCount() - 1); + } + } + + private void positionItems(final int dx) { + if (getChildCount() > 0) { + mDisplayOffset += dx; + int left = mDisplayOffset; + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + int childWidth = child.getMeasuredWidth(); + child.layout(left, 0, left + childWidth, + child.getMeasuredHeight()); + left += childWidth + child.getPaddingRight(); + } + } + } + + public synchronized void scrollTo(int x) { + synchronized (HorizontalListView.this) { + mNextX = x; + } + requestLayout(); + } + + public synchronized void flingBy(float velocityX) { + + synchronized (HorizontalListView.this) { + mScroller.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0); + } + requestLayout(); + } + + public int getScrollPositionX() { + return mNextX; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + boolean handled = super.dispatchTouchEvent(ev); + handled |= mGesture.onTouchEvent(ev); + return handled; + } + + protected boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + m_scrolling = true; + + synchronized (HorizontalListView.this) { + mScroller.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0); + } + requestLayout(); + + return true; + } + + protected boolean onDown(MotionEvent e) { + m_scrolling = false; + mScroller.forceFinished(true); + return true; + } + + protected void onScroll(MotionEvent e1, MotionEvent e2, float distanceX, + float distanceY) { + m_scrolling = true; + } + + private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() { + + @Override + public boolean onDown(MotionEvent e) { + return HorizontalListView.this.onDown(e); + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, + float velocityY) { + return HorizontalListView.this + .onFling(e1, e2, velocityX, velocityY); + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + HorizontalListView.this.onScroll(e1, e2, distanceX, distanceY); + synchronized (HorizontalListView.this) { + mNextX += (int) distanceX; + } + requestLayout(); + + return true; + } + + public void onShowPress(MotionEvent e) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (isEventWithinView(e, child)) { + selectedIndex = i; + return; + } + } + }; + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + for (int i = 0; i < getChildCount(); i++) { + View child = getChildAt(i); + if (isEventWithinView(e, child)) { + if (mOnItemClicked != null) { + mOnItemClicked.onItemClick(HorizontalListView.this, + child, mLeftViewIndex + 1 + i, + mAdapter.getItemId(mLeftViewIndex + 1 + i)); + } + if (mOnItemSelected != null) { + mOnItemSelected.onItemSelected(HorizontalListView.this, + child, mLeftViewIndex + 1 + i, + mAdapter.getItemId(mLeftViewIndex + 1 + i)); + } + break; + } + + } + return true; + } + + @Override + public void onLongPress(MotionEvent e) { + int childCount = getChildCount(); + for (int i = 0; i < childCount; i++) { + View child = getChildAt(i); + if (isEventWithinView(e, child)) { + if (mOnItemLongClicked != null && !m_scrolling) { + mOnItemLongClicked.onItemLongClick( + HorizontalListView.this, child, mLeftViewIndex + + 1 + i, + mAdapter.getItemId(mLeftViewIndex + 1 + i)); + } + break; + } + + } + } + + private boolean isEventWithinView(MotionEvent e, View child) { + Rect viewRect = new Rect(); + int[] childPosition = new int[2]; + child.getLocationOnScreen(childPosition); + int left = childPosition[0]; + int right = left + child.getWidth(); + int top = childPosition[1]; + int bottom = top + child.getHeight(); + viewRect.set(left, top, right, bottom); + return viewRect.contains((int) e.getRawX(), (int) e.getRawY()); + } + }; + +} \ No newline at end of file