PageRenderTime 75ms CodeModel.GetById 36ms RepoModel.GetById 0ms app.codeStats 0ms

/src/br/com/carlosrafaelgn/fplay/list/RadioStationList.java

https://gitlab.com/madamovic-bg/FPlayAndroid
Java | 832 lines | 745 code | 35 blank | 52 comment | 215 complexity | a732e7fd4b387c406b29dd45b571d574 MD5 | raw file
  1. //
  2. // FPlayAndroid is distributed under the FreeBSD License
  3. //
  4. // Copyright (c) 2013-2014, Carlos Rafael Gimenes das Neves
  5. // All rights reserved.
  6. //
  7. // Redistribution and use in source and binary forms, with or without
  8. // modification, are permitted provided that the following conditions are met:
  9. //
  10. // 1. Redistributions of source code must retain the above copyright notice, this
  11. // list of conditions and the following disclaimer.
  12. // 2. Redistributions in binary form must reproduce the above copyright notice,
  13. // this list of conditions and the following disclaimer in the documentation
  14. // and/or other materials provided with the distribution.
  15. //
  16. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  17. // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
  20. // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. //
  27. // The views and conclusions contained in the software and documentation are those
  28. // of the authors and should not be interpreted as representing official policies,
  29. // either expressed or implied, of the FreeBSD Project.
  30. //
  31. // https://github.com/carlosrafaelgn/FPlayAndroid
  32. //
  33. package br.com.carlosrafaelgn.fplay.list;
  34. import android.content.Context;
  35. import android.os.Message;
  36. import android.view.View;
  37. import android.view.ViewGroup;
  38. import org.xmlpull.v1.XmlPullParser;
  39. import org.xmlpull.v1.XmlPullParserFactory;
  40. import java.io.BufferedInputStream;
  41. import java.io.BufferedOutputStream;
  42. import java.io.FileInputStream;
  43. import java.io.FileNotFoundException;
  44. import java.io.FileOutputStream;
  45. import java.io.IOException;
  46. import java.io.InputStream;
  47. import java.net.HttpURLConnection;
  48. import java.net.URL;
  49. import java.net.URLEncoder;
  50. import java.util.HashSet;
  51. import br.com.carlosrafaelgn.fplay.R;
  52. import br.com.carlosrafaelgn.fplay.activity.MainHandler;
  53. import br.com.carlosrafaelgn.fplay.playback.Player;
  54. import br.com.carlosrafaelgn.fplay.ui.RadioStationView;
  55. import br.com.carlosrafaelgn.fplay.ui.UI;
  56. import br.com.carlosrafaelgn.fplay.util.ArraySorter;
  57. import br.com.carlosrafaelgn.fplay.util.Serializer;
  58. public final class RadioStationList extends BaseList<RadioStation> implements Runnable, ArraySorter.Comparer<RadioStation>, MainHandler.Callback {
  59. public interface RadioStationAddedObserver {
  60. void onRadioStationAdded();
  61. }
  62. //after analyzing the results obtained from http://dir.xiph.org/xxx
  63. //I noticed that there are never more than 5 pages of results,
  64. //with 20 results each ;)
  65. private static final int MAX_COUNT = 100;
  66. private static final int MSG_FINISHED = 0x0300;
  67. private static final int MSG_MORE_RESULTS = 0x0301;
  68. //public static final int POPULAR_GENRE_COUNT = 32;
  69. //I took these genres from http://dir.xiph.org/yp.xml
  70. //... after grouping, counting, sorting and selecting properly ;)
  71. public static final String[] GENRES = new String[] {
  72. "8bit",
  73. "Alternative",
  74. "Anime",
  75. "Christian",
  76. "Classic",
  77. "Classical",
  78. "Dance",
  79. "Disco",
  80. "Electronic",
  81. "Hits",
  82. "House",
  83. "Jazz",
  84. "Lounge",
  85. "Metal",
  86. "Misc",
  87. "Music",
  88. "News",
  89. "Oldies",
  90. "Pop",
  91. "Radio",
  92. "Reggae",
  93. "Rock",
  94. "Salsa",
  95. "Ska",
  96. "Talk",
  97. "Techno",
  98. "Top",
  99. "Top40",
  100. "Top100",
  101. "Trance",
  102. "Various",
  103. "Video Game", //last popular genre
  104. "40s",
  105. "50s",
  106. "60s",
  107. "70s",
  108. "80s",
  109. "90s",
  110. "00s",
  111. "Adult",
  112. "Alternate",
  113. "Ambiance",
  114. "Ambient",
  115. "Argentina",
  116. "Baladas",
  117. "Bass",
  118. "Beatles",
  119. "Bible",
  120. "Blues",
  121. "Broadway",
  122. "Catholic",
  123. "Celtic",
  124. "Chill",
  125. "Chillout",
  126. "Chiptunes",
  127. "Club",
  128. "Comedy",
  129. "Contemporary",
  130. "Country",
  131. "Downtempo",
  132. "Dubstep",
  133. "Easy",
  134. "Eclectic",
  135. "Electro",
  136. "Electronica",
  137. "Elektro",
  138. "Eurodance",
  139. "Experimental",
  140. "Folk",
  141. "France",
  142. "Funk",
  143. "German",
  144. "Gospel",
  145. "Goth",
  146. "Hardcore",
  147. "Hardstyle",
  148. "Hindi",
  149. "Hiphop",
  150. "Hit",
  151. "Ibiza",
  152. "Indie",
  153. "Industrial",
  154. "Inspirational",
  155. "Instrumental",
  156. "International",
  157. "Italia",
  158. "Japan",
  159. "Jpop",
  160. "Jrock",
  161. "Jungle",
  162. "Korea",
  163. "Kpop",
  164. "Latin",
  165. "Latina",
  166. "Latinpop",
  167. "Layback",
  168. "Libre",
  169. "Live",
  170. "Lovesongs",
  171. "Mariachi",
  172. "Mashup",
  173. "Merengue",
  174. "Minecraft",
  175. "Mixed",
  176. "Modern",
  177. "Motown",
  178. "Mozart",
  179. "Musica",
  180. "Nederlands",
  181. "New",
  182. "Oldschool",
  183. "Paris",
  184. "Progressive",
  185. "Psytrance",
  186. "Punk",
  187. "Punkrock",
  188. "Rap",
  189. "Recuerdos",
  190. "Reggaeton",
  191. "Relax",
  192. "Remixes",
  193. "Rockabilly",
  194. "Romantica",
  195. "Roots",
  196. "Russian",
  197. "Schlager",
  198. "Sertanejo",
  199. "Slow",
  200. "Smooth",
  201. "Soul",
  202. "Soundtrack",
  203. "Southern",
  204. "Sports",
  205. "Student",
  206. "Tech",
  207. "Tropical",
  208. "Webradio",
  209. "Western",
  210. "World",
  211. "Zen",
  212. "Zouk"
  213. };
  214. private boolean loading, favoritesLoaded, favoritesChanged;
  215. private final Object favoritesSync;
  216. private final HashSet<RadioStation> favorites;
  217. private final String tags, noOnAir, noDescription, noTags;
  218. private volatile boolean readyToFetch, isSavingFavorites;
  219. private volatile int version;
  220. private volatile String genreToFetch, searchTermToFetch;
  221. private volatile Context context;
  222. public RadioStationAddedObserver radioStationAddedObserver;
  223. public RadioStationList(String tags, String noOnAir, String noDescription, String noTags) {
  224. super(RadioStation.class, MAX_COUNT);
  225. this.items = new RadioStation[MAX_COUNT];
  226. this.readyToFetch = true;
  227. this.favoritesSync = new Object();
  228. this.favorites = new HashSet<>(32);
  229. this.tags = tags;
  230. this.noOnAir = noOnAir;
  231. this.noDescription = noDescription;
  232. this.noTags = noTags;
  233. }
  234. public boolean isLoading() {
  235. return loading;
  236. }
  237. private void loadingProcessChanged(boolean started) {
  238. loading = started;
  239. if (UI.browserActivity != null)
  240. UI.browserActivity.loadingProcessChanged(started);
  241. }
  242. public void cancel() {
  243. version++;
  244. if (loading)
  245. loadingProcessChanged(false);
  246. }
  247. private static String readStringIfPossible(XmlPullParser parser, StringBuilder sb) throws Throwable {
  248. sb.delete(0, sb.length());
  249. switch (parser.getEventType()) {
  250. case XmlPullParser.COMMENT:
  251. break;
  252. case XmlPullParser.ENTITY_REF:
  253. break;
  254. case XmlPullParser.IGNORABLE_WHITESPACE:
  255. sb.append(' ');
  256. break;
  257. case XmlPullParser.PROCESSING_INSTRUCTION:
  258. case XmlPullParser.TEXT:
  259. if (parser.isWhitespace())
  260. sb.append(' ');
  261. else
  262. sb.append(parser.getText());
  263. break;
  264. default:
  265. return null;
  266. }
  267. for (; ; ) {
  268. switch (parser.nextToken()) {
  269. case XmlPullParser.COMMENT:
  270. break;
  271. case XmlPullParser.ENTITY_REF:
  272. break;
  273. case XmlPullParser.IGNORABLE_WHITESPACE:
  274. sb.append(' ');
  275. break;
  276. case XmlPullParser.PROCESSING_INSTRUCTION:
  277. case XmlPullParser.TEXT:
  278. if (parser.isWhitespace())
  279. sb.append(' ');
  280. else
  281. sb.append(parser.getText());
  282. break;
  283. default:
  284. return sb.toString();
  285. }
  286. }
  287. }
  288. private static boolean parseIcecastColumn2(XmlPullParser parser, String[] fields) throws Throwable {
  289. boolean hasFields = false, linkContainsType = false;
  290. int ev;
  291. String v;
  292. while ((ev = parser.nextToken()) != XmlPullParser.END_DOCUMENT) {
  293. if (ev == XmlPullParser.END_TAG && parser.getName().equals("td"))
  294. break;
  295. if (ev == XmlPullParser.START_TAG) {
  296. if (parser.getName().equals("p") && hasFields) {
  297. linkContainsType = true;
  298. } else if (parser.getName().equals("a")) {
  299. if (linkContainsType) {
  300. if (parser.nextToken() != XmlPullParser.TEXT) {
  301. //impossible to determine the type of the stream...
  302. //just drop it!
  303. hasFields = false;
  304. } else {
  305. v = parser.getText().trim();
  306. hasFields = (v.equals("MP3") || v.equals("Ogg Vorbis"));
  307. fields[2] = v;
  308. }
  309. } else {
  310. for (int a = parser.getAttributeCount() - 1; a >= 0; a--) {
  311. if (parser.getAttributeName(a).equals("href") &&
  312. (v = parser.getAttributeValue(a)).endsWith("m3u")) {
  313. fields[7] = ((v.charAt(0) == '/') ? ("http://dir.xiph.org" + v) : (v)).trim();
  314. hasFields = true;
  315. break;
  316. }
  317. }
  318. }
  319. }
  320. }
  321. }
  322. return hasFields;
  323. }
  324. private boolean parseIcecastColumn1(XmlPullParser parser, String[] fields, StringBuilder sb) throws Throwable {
  325. int ev = 0, pCount = 0;
  326. boolean hasFields = false, hasNextToken = false, parsingTags = false;
  327. String str;
  328. while (hasNextToken || ((ev = parser.nextToken()) != XmlPullParser.END_DOCUMENT)) {
  329. hasNextToken = false;
  330. if (ev == XmlPullParser.END_TAG && parser.getName().equals("td"))
  331. break;
  332. if (ev == XmlPullParser.START_TAG && parser.getName().equals("p")) {
  333. pCount++;
  334. } else if (ev == XmlPullParser.START_TAG && parser.getName().equals("ul")) {
  335. parsingTags = true;
  336. sb.delete(0, sb.length());
  337. } else if (parsingTags) {
  338. if (ev == XmlPullParser.START_TAG && parser.getName().equals("a")) {
  339. if (parser.nextToken() == XmlPullParser.TEXT) {
  340. if (sb.length() > 0) {
  341. sb.append(' ');
  342. } else {
  343. sb.append(tags);
  344. sb.append(": ");
  345. }
  346. sb.append(parser.getText());
  347. } else {
  348. hasNextToken = true;
  349. ev = parser.getEventType();
  350. }
  351. } else if (ev == XmlPullParser.END_TAG && parser.getName().equals("ul")) {
  352. hasFields = true;
  353. fields[6] = sb.toString().trim();
  354. }
  355. } else {
  356. switch (pCount) {
  357. case 1:
  358. if (ev == XmlPullParser.START_TAG) {
  359. if (parser.getName().equals("a")) {
  360. for (int a = parser.getAttributeCount() - 1; a >= 0; a--) {
  361. if (parser.getAttributeName(a).equals("href")) {
  362. fields[1] = parser.getAttributeValue(a).trim();
  363. //set hasFields to true, only if the title has been found!
  364. //hasFields = true;
  365. break;
  366. }
  367. }
  368. parser.nextToken();
  369. if ((str = readStringIfPossible(parser, sb)) != null) {
  370. hasFields = true;
  371. fields[0] = str.trim();
  372. }
  373. hasNextToken = true;
  374. ev = parser.getEventType();
  375. } else if (fields[0].length() != 0 && parser.getName().equals("span")) {
  376. if (parser.nextToken() == XmlPullParser.TEXT) {
  377. fields[3] = parser.getText().trim();
  378. if (fields[3].length() > 0)
  379. fields[3] = fields[3].substring(1).trim();
  380. } else {
  381. hasNextToken = true;
  382. ev = parser.getEventType();
  383. }
  384. }
  385. }
  386. break;
  387. case 2:
  388. if (fields[4].length() == 0 && (str = readStringIfPossible(parser, sb)) != null) {
  389. hasFields = true;
  390. fields[4] = str.trim();
  391. hasNextToken = true;
  392. ev = parser.getEventType();
  393. } else {
  394. hasNextToken = false;
  395. }
  396. break;
  397. case 3:
  398. if (ev == XmlPullParser.END_TAG && parser.getName().equals("strong")) {
  399. if (fields[5].length() == 0) {
  400. parser.nextToken();
  401. if ((str = readStringIfPossible(parser, sb)) != null) {
  402. hasFields = true;
  403. fields[5] = str.trim();
  404. }
  405. hasNextToken = true;
  406. ev = parser.getEventType();
  407. }
  408. }
  409. break;
  410. }
  411. }
  412. }
  413. return hasFields;
  414. }
  415. private boolean parseIcecastRow(XmlPullParser parser, String[] fields, StringBuilder sb) throws Throwable {
  416. fields[0] = ""; //title
  417. fields[1] = ""; //uri
  418. fields[2] = ""; //type
  419. fields[3] = ""; //listeners
  420. fields[4] = ""; //description
  421. fields[5] = ""; //onAir
  422. fields[6] = ""; //tags
  423. fields[7] = ""; //m3uUri
  424. int ev, colCount = 0;
  425. while ((ev = parser.nextToken()) != XmlPullParser.END_DOCUMENT && colCount < 2) {
  426. if (ev == XmlPullParser.END_TAG && parser.getName().equals("tr"))
  427. break;
  428. if (ev == XmlPullParser.START_TAG && parser.getName().equals("td")) {
  429. colCount++;
  430. if (colCount == 1) {
  431. if (!parseIcecastColumn1(parser, fields, sb))
  432. return false;
  433. } else {
  434. if (!parseIcecastColumn2(parser, fields))
  435. return false;
  436. }
  437. }
  438. }
  439. return true;
  440. }
  441. private boolean parseIcecastResults(InputStream is, String[] fields, int myVersion, StringBuilder sb, int[] currentStationIndex) throws Throwable {
  442. int b = 0;
  443. while (b >= 0) {
  444. if ((b = is.read()) == (int)'<' &&
  445. (b = is.read()) == (int)'h' &&
  446. (b = is.read()) == (int)'2' &&
  447. (b = is.read()) == (int)'>')
  448. break;
  449. }
  450. if (b < 0)
  451. return false;
  452. while (b >= 0) {
  453. if ((b = is.read()) == (int)'<' &&
  454. (b = is.read()) == (int)'/' &&
  455. (b = is.read()) == (int)'h' &&
  456. (b = is.read()) == (int)'2' &&
  457. (b = is.read()) == (int)'>')
  458. break;
  459. }
  460. if (b < 0)
  461. return false;
  462. boolean hasResults = false;
  463. //According to these docs, kXML parser will accept some XML documents
  464. //that should actually be rejected (A robust "relaxed" mode for parsing
  465. //HTML or SGML files):
  466. //http://developer.android.com/training/basics/network-ops/xml.html
  467. //http://kxml.org/index.html
  468. try {
  469. XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
  470. XmlPullParser parser = factory.newPullParser();
  471. parser.setInput(is, "UTF-8");
  472. //special feature! (check out kXML2 source and you will find it!)
  473. parser.setFeature("http://xmlpull.org/v1/doc/features.html#relaxed", true);
  474. int ev;
  475. while ((ev = parser.nextToken()) != XmlPullParser.END_DOCUMENT && currentStationIndex[0] < MAX_COUNT) {
  476. if (ev == XmlPullParser.END_TAG && parser.getName().equals("table"))
  477. break;
  478. if (ev == XmlPullParser.START_TAG && parser.getName().equals("tr")) {
  479. if (myVersion != version)
  480. break;
  481. if (parseIcecastRow(parser, fields, sb) && myVersion == version) {
  482. final RadioStation station = new RadioStation(fields[0], fields[1], fields[2], fields[4].length() == 0 ? noDescription : fields[4], fields[5].length() == 0 ? noOnAir : fields[5], fields[6].length() == 0 ? noTags : fields[6], fields[7], false);
  483. synchronized (favoritesSync) {
  484. station.isFavorite = favorites.contains(station);
  485. }
  486. if (myVersion != version)
  487. break;
  488. items[currentStationIndex[0]++] = station;
  489. hasResults = true;
  490. }
  491. }
  492. }
  493. } catch (Throwable ex) {
  494. ex.printStackTrace();
  495. }
  496. return hasResults;
  497. }
  498. private void loadFavoritesInternal(Context context) throws IOException {
  499. FileInputStream fs = null;
  500. BufferedInputStream bs = null;
  501. try {
  502. fs = context.openFileInput("_RadioFav");
  503. bs = new BufferedInputStream(fs, 4096);
  504. final int version = Serializer.deserializeInt(bs);
  505. final int count = Math.min(Serializer.deserializeInt(bs), MAX_COUNT);
  506. if (version == 0x0100 && count > 0) {
  507. favorites.clear();
  508. for (int i = 0; i < count; i++)
  509. favorites.add(RadioStation.deserialize(bs, true));
  510. }
  511. } catch (IOException ex) {
  512. if (ex instanceof FileNotFoundException && fs == null) {
  513. favorites.clear();
  514. } else {
  515. throw ex;
  516. }
  517. } finally {
  518. try {
  519. if (bs != null)
  520. bs.close();
  521. } catch (Throwable ex) {
  522. ex.printStackTrace();
  523. }
  524. try {
  525. if (fs != null)
  526. fs.close();
  527. } catch (Throwable ex) {
  528. ex.printStackTrace();
  529. }
  530. }
  531. }
  532. private void saveFavoritesInternal(Context context) throws IOException {
  533. FileOutputStream fs = null;
  534. BufferedOutputStream bs = null;
  535. try {
  536. final int count = Math.min(MAX_COUNT, favorites.size());
  537. int i = 0;
  538. fs = context.openFileOutput("_RadioFav", 0);
  539. bs = new BufferedOutputStream(fs, 4096);
  540. Serializer.serializeInt(bs, 0x0100);
  541. Serializer.serializeInt(bs, count);
  542. for (RadioStation s : favorites) {
  543. if (i >= count)
  544. break;
  545. s.serialize(bs);
  546. i++;
  547. }
  548. bs.flush();
  549. } finally {
  550. try {
  551. if (bs != null)
  552. bs.close();
  553. } catch (Throwable ex) {
  554. ex.printStackTrace();
  555. }
  556. try {
  557. if (fs != null)
  558. fs.close();
  559. } catch (Throwable ex) {
  560. ex.printStackTrace();
  561. }
  562. }
  563. }
  564. @Override
  565. public int compare(RadioStation a, RadioStation b) {
  566. int r = a.title.compareToIgnoreCase(b.title);
  567. if (r != 0)
  568. return r;
  569. r = a.onAir.compareToIgnoreCase(b.onAir);
  570. if (r != 0)
  571. return r;
  572. return a.m3uUri.compareTo(b.m3uUri);
  573. }
  574. @Override
  575. public void run() {
  576. final int myVersion = version;
  577. final Context context = this.context;
  578. final String genre = genreToFetch, searchTerm = searchTermToFetch;
  579. final boolean isSavingFavorites = this.isSavingFavorites;
  580. this.context = null;
  581. readyToFetch = true;
  582. int err = 0;
  583. if (!favoritesLoaded && !isSavingFavorites && context != null) {
  584. synchronized (favoritesSync) {
  585. if (!favoritesLoaded) {
  586. try {
  587. loadFavoritesInternal(context);
  588. favoritesLoaded = true;
  589. favoritesChanged = false;
  590. } catch (Throwable ex) {
  591. ex.printStackTrace();
  592. }
  593. }
  594. }
  595. }
  596. if (genre == null && searchTerm == null) {
  597. //favorites
  598. synchronized (favoritesSync) {
  599. if (isSavingFavorites) {
  600. try {
  601. if (favoritesLoaded && favoritesChanged && context != null) {
  602. saveFavoritesInternal(context);
  603. favoritesChanged = false;
  604. }
  605. } catch (Throwable ex) {
  606. MainHandler.toast(R.string.error_gen);
  607. }
  608. } else {
  609. try {
  610. if (favoritesLoaded && context != null) {
  611. if (myVersion != version)
  612. return;
  613. final RadioStation[] stations = new RadioStation[favorites.size()];
  614. favorites.toArray(stations);
  615. ArraySorter.sort(stations, 0, stations.length, this);
  616. if (myVersion == version) {
  617. final int count = Math.min(stations.length, MAX_COUNT);
  618. System.arraycopy(stations, 0, items, 0, count);
  619. MainHandler.sendMessage(RadioStationList.this, MSG_MORE_RESULTS, myVersion, count);
  620. }
  621. }
  622. } catch (Throwable ex) {
  623. err = -2;
  624. } finally {
  625. if (myVersion == version)
  626. MainHandler.sendMessage(RadioStationList.this, MSG_FINISHED, myVersion, err);
  627. }
  628. }
  629. }
  630. return;
  631. }
  632. try {
  633. int pageNumber = 0;
  634. boolean hasResults;
  635. String[] fields = new String[8];
  636. final StringBuilder sb = new StringBuilder(256);
  637. final int[] currentStationIndex = { 0 };
  638. //genre MUST be one of the predefined genres (due to the encoding)
  639. final String uri = ((genre != null) ?
  640. ("http://dir.xiph.org/by_genre/" + genre.replace(" ", "%20") + "?page=") :
  641. ("http://dir.xiph.org/search?search=" + URLEncoder.encode(searchTerm, "UTF-8") + "&page="));
  642. do {
  643. if (myVersion != version)
  644. break;
  645. InputStream is = null;
  646. HttpURLConnection urlConnection = null;
  647. try {
  648. urlConnection = (HttpURLConnection)(new URL(uri + pageNumber)).openConnection();
  649. if (myVersion != version)
  650. break;
  651. err = urlConnection.getResponseCode();
  652. if (err == 200) {
  653. is = urlConnection.getInputStream();
  654. hasResults = parseIcecastResults(is, fields, myVersion, sb, currentStationIndex);
  655. if (hasResults && myVersion == version)
  656. MainHandler.sendMessage(RadioStationList.this, MSG_MORE_RESULTS, myVersion, currentStationIndex[0]);
  657. err = 0;
  658. } else {
  659. hasResults = false;
  660. }
  661. } catch (Throwable ex) {
  662. hasResults = false;
  663. err = -1;
  664. } finally {
  665. try {
  666. if (urlConnection != null)
  667. urlConnection.disconnect();
  668. } catch (Throwable ex) {
  669. ex.printStackTrace();
  670. }
  671. try {
  672. if (is != null)
  673. is.close();
  674. } catch (Throwable ex) {
  675. ex.printStackTrace();
  676. }
  677. System.gc();
  678. }
  679. pageNumber++;
  680. } while (hasResults && pageNumber < 5);
  681. } catch (Throwable ex) {
  682. err = -1;
  683. } finally {
  684. if (myVersion == version)
  685. MainHandler.sendMessage(RadioStationList.this, MSG_FINISHED, myVersion, err);
  686. }
  687. }
  688. public void addFavoriteStation(RadioStation station) {
  689. synchronized (favoritesSync) {
  690. if (favoritesLoaded) {
  691. station.isFavorite = true;
  692. favoritesChanged |= favorites.add(station);
  693. }
  694. }
  695. }
  696. public void removeFavoriteStation(RadioStation station) {
  697. synchronized (favoritesSync) {
  698. if (favoritesLoaded) {
  699. station.isFavorite = false;
  700. favoritesChanged |= favorites.remove(station);
  701. }
  702. }
  703. }
  704. public void fetchIcecast(Context context, String genre, String searchTerm) {
  705. while (!readyToFetch)
  706. Thread.yield();
  707. cancel();
  708. clear();
  709. loadingProcessChanged(true);
  710. final Thread t = new Thread(this, "Icecast Station Fetcher Thread");
  711. isSavingFavorites = false;
  712. genreToFetch = genre;
  713. searchTermToFetch = searchTerm;
  714. this.context = context;
  715. readyToFetch = false;
  716. try {
  717. t.start();
  718. } catch (Throwable ex) {
  719. readyToFetch = true;
  720. loadingProcessChanged(false);
  721. }
  722. }
  723. public void fetchFavorites(Context context) {
  724. while (!readyToFetch)
  725. Thread.yield();
  726. cancel();
  727. clear();
  728. loadingProcessChanged(true);
  729. final Thread t = new Thread(this, "Icecast Favorite Stations Fetcher Thread");
  730. isSavingFavorites = false;
  731. genreToFetch = null;
  732. searchTermToFetch = null;
  733. this.context = context;
  734. readyToFetch = false;
  735. try {
  736. t.start();
  737. } catch (Throwable ex) {
  738. readyToFetch = true;
  739. loadingProcessChanged(false);
  740. }
  741. }
  742. public void saveFavorites(Context context) {
  743. while (!readyToFetch)
  744. Thread.yield();
  745. synchronized (favoritesSync) {
  746. if (!favoritesLoaded || !favoritesChanged)
  747. return;
  748. }
  749. final Thread t = new Thread(this, "Icecast Favorite Stations Storer Thread");
  750. isSavingFavorites = true;
  751. genreToFetch = null;
  752. searchTermToFetch = null;
  753. this.context = context;
  754. readyToFetch = false;
  755. try {
  756. t.start();
  757. } catch (Throwable ex) {
  758. readyToFetch = true;
  759. }
  760. }
  761. @Override
  762. public boolean handleMessage(Message msg) {
  763. if (msg.arg1 != version)
  764. return true;
  765. switch (msg.what) {
  766. case MSG_FINISHED:
  767. loadingProcessChanged(false);
  768. if (msg.arg2 != 0)
  769. UI.toast(Player.getService(), ((msg.arg2 != -2) && !Player.isConnectedToTheInternet()) ? R.string.error_connection : R.string.error_gen);
  770. break;
  771. case MSG_MORE_RESULTS:
  772. //protection against out of order messages... does this really happen? ;)
  773. if (msg.arg2 > count) {
  774. //items are always appended :)
  775. modificationVersion++;
  776. final int c = count;
  777. count = msg.arg2;
  778. addingItems(c, c - count);
  779. notifyDataSetChanged(-1, CONTENT_ADDED);
  780. if (radioStationAddedObserver != null)
  781. radioStationAddedObserver.onRadioStationAdded();
  782. }
  783. break;
  784. }
  785. return true;
  786. }
  787. @Override
  788. public View getView(int position, View convertView, ViewGroup parent) {
  789. final RadioStationView view = ((convertView != null) ? (RadioStationView)convertView : new RadioStationView(Player.getService()));
  790. view.setItemState(items[position], position, getItemState(position));
  791. return view;
  792. }
  793. @Override
  794. public int getViewHeight() {
  795. return RadioStationView.getViewHeight();
  796. }
  797. }