/* * @copyright 2011 Philip Warner * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue 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. * * Book Catalogue 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 Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import com.eleybourn.bookcatalogue.utils.Logger; import com.eleybourn.bookcatalogue.utils.Utils; /** * Handle all aspects of searching (and ultimately synchronizing with) LibraryThing. * * The basic URLs are: * * Details via ISBN: http://www.librarything.com/services/rest/1.1/?method=librarything.ck.getwork&apikey=<DEVKEY>&isbn=<ISBN> * Covers via ISBN: http://covers.librarything.com/devkey/<DEVKEY>/large/isbn/<ISBN> * * ENHANCE: extend the use of LibraryThing: * - Lookup title using keywords: http://www.librarything.com/api/thingTitle/hand oberon * - consider scraping html for covers: http://www.librarything.com/work/18998/covers * * @author Philip Warner * */ public class LibraryThingManager { /** App context (for prefs) */ Context mAppContext; /** Name of preference that contains the dev key for the user */ public static final String LT_DEVKEY_PREF_NAME = "lt_devkey"; /** Name of preference that controls display of alert about LT */ public static final String LT_HIDE_ALERT_PREF_NAME = "lt_hide_alert"; private static Long mLastRequestTime = 0L; // Words in XML public static String ID = "id"; public static String AUTHOR = "author"; public static String RESPONSE = "response"; public static String FIELD = "field"; public static String ISBN = "isbn"; public static String ITEM = "item"; public static String FACT = "fact"; public static String CANONICAL_TITLE = "canonicaltitle"; public static String SERIES = "series"; public static String PLACES = "placesmentioned"; public static String CHARACTERS = "characternames"; public static String COVER_URL_LARGE = "http://covers.librarything.com/devkey/%1$s/large/isbn/%2$s"; public static String COVER_URL_MEDIUM = "http://covers.librarything.com/devkey/%1$s/medium/isbn/%2$s"; public static String COVER_URL_SMALL = "http://covers.librarything.com/devkey/%1$s/small/isbn/%2$s"; public static String DETAIL_URL = "http://www.librarything.com/services/rest/1.1/?method=librarything.ck.getwork&apikey=%1$s&isbn=%2$s"; public static String EDITIONS_URL = "http://www.librarything.com/api/thingISBN/%s"; // Field types we are interested in. private enum FieldTypes{ NONE, AUTHOR, TITLE, SERIES, PLACES, CHARACTERS, OTHER }; // Sizes of thumbnails public enum ImageSizes { SMALL, MEDIUM, LARGE }; public LibraryThingManager(Context context) { mAppContext = context.getApplicationContext(); } /** * Use mLastRequestTime to determine how long until the next request is allowed; and * update mLastRequestTime this needs to be synchroized across threads. * * Note that as a result of this approach mLastRequestTime may in fact be * in the future; callers to this routine effectively allocate time slots. * * This method will sleep() until it can make a request; if ten threads call this * simultaneously, one will return immediately, one will return 1 second later, another * two seconds etc. * */ private static void waitUntilRequestAllowed() { long now = System.currentTimeMillis(); long wait; synchronized(mLastRequestTime) { wait = 1000 - (now - mLastRequestTime); // // mLastRequestTime must be updated while synchronized. As soon as this // block is left, another block may perform another update. // if (wait < 0) wait = 0; mLastRequestTime = now + wait; } if (wait > 0) { try { Thread.sleep(wait); } catch (InterruptedException e) { } } } /** * Search LibaryThing for an ISBN using the Web API. * * @param isbn ISBN to lookup * @param bookData COllection to save results in * * A typical (and thorough) LibraryThing ISBN response looks like (with formatting added): * * <?xml version="1.0" encoding="UTF-8"?> * <response stat="ok"> * <ltml xmlns="http://www.librarything.com/" version="1.1"> * <item id="5196084" type="work"> * <author id="28" authorcode="asimovisaac">Isaac Asimov</author> * <url>http://www.librarything.com/work/5196084</url> * <commonknowledge> * <fieldList> * <field type="4" name="awards" displayName="Awards and honors"> * <versionList> * <version id="3324305" archived="0" lang="eng"> * <date timestamp="1296476301">Mon, 31 Jan 2011 07:18:21 -0500</date> * <person id="325052"><name>Cecrow</name><url>http://www.librarything.com/profile/Cecrow</url></person> * <factList> * <fact>1001 Books You Must Read Before You Die (2006/2008/2010 Edition)</fact> * <fact>Astounding/Analog Science Fiction and Fact All-Time Poll (placed 23, 1952)</fact> * <fact>Astounding/Analog Science Fiction and Fact All-Time Poll (placed 21, 1956)</fact> * <fact>Harenberg Buch der 1000 B�cher (1. Ausgabe)</fact> * <fact>501 Must-Read Books (Science Fiction)</fact> * </factList> * </version> * </versionList> * </field> * <field type="37" name="movies" displayName="Related movies"> * <versionList> * <version id="3120269" archived="0" lang="eng"> * <date timestamp="1292202792">Sun, 12 Dec 2010 20:13:12 -0500</date> * <person id="656066"> * <name>Scottneumann</name> * <url>http://www.librarything.com/profile/Scottneumann</url> * </person> * <factList> * <fact>Robots (1988 | tt0174170)</fact> * <fact>I, Robot (2004 | tt0343818)</fact> * <fact>The Outer Limits: I Robot (1963 | tt0056777)</fact> * </factList> * </version> * </versionList> * </field> * <field type="40" name="publisherseries" displayName="Publisher Series"> * <versionList> * <version id="2971007" archived="0" lang="eng"> * <date timestamp="1289497446">Thu, 11 Nov 2010 12:44:06 -0500</date> * <person id="3929"> * <name>sonyagreen</name> * <url>http://www.librarything.com/profile/sonyagreen</url> * </person> * <factList> * <fact>Voyager Classics</fact> * </factList> * </version> * </versionList> * </field> * <field type="14" name="description" displayName="Description"> * <versionList> * <version id="2756634" archived="0" lang="eng"> * <date timestamp="1281897478">Sun, 15 Aug 2010 14:37:58 -0400</date> * <person id="203279"> * <name>jseger9000</name> * <url>http://www.librarything.com/profile/jseger9000</url> * </person> * <factList> * <fact><![CDATA[ Contents:<br><br>Introduction<br>Robbie<br>Runaround< * ;br>Reason<br>Catch That Rabbit<br>Liar!<br>Little Lost Robot< * ;br>Escape!<br>Evidence<br>The Evitable Conflict ]]> * </fact> * </factList> * </version> * </versionList> * </field> * <field type="23" name="series" displayName="Series"> * <versionList> * <version id="2742329" archived="0" lang="eng"> * <date timestamp="1281338643">Mon, 09 Aug 2010 03:24:03 -0400</date> * <person id="1162290"> * <name>larry.auld</name> * <url>http://www.librarything.com/profile/larry.auld</url> * </person> * <factList> * <fact>Isaac Asimov's Robot Series (0.1)</fact> * <fact>Robot/Foundation</fact> * <fact>Robot/Empire/Foundation - Chronological (book 1)</fact> * </factList> * </version> * </versionList> * </field> * <field type="16" name="originalpublicationdate" displayName="Original publication date"> * <versionList> * <version id="2554955" archived="0" lang="eng"> * <date timestamp="1275746736">Sat, 05 Jun 2010 10:05:36 -0400</date> * <person id="125174"> * <name>paulhurtley</name> * <url>http://www.librarything.com/profile/paulhurtley</url> * </person> * <factList> * <fact>1950 (Collection)</fact> * <fact>1944 (Catch that Rabbit)</fact> * <fact>1945 (Escape!)</fact> * <fact>1946 (Evidence)</fact> * <fact>1950 (The Evitable Conflict)</fact> * <fact>1941 (Liar)</fact> * <fact>1947 (Little Lost Robot)</fact> * <fact>1940 (Robbie)</fact> * <fact>1942 (Runaround)</fact> * <fact>1941 (Reason)</fact> * </factList> * </version> * </versionList> * </field> * <field type="27" name="quotations" displayName="Quotations"> * <versionList> * <version id="2503597" archived="0" lang="eng"> * <date timestamp="1274377341">Thu, 20 May 2010 13:42:21 -0400</date> * <person id="1797"> * <name>lorax</name> * <url>http://www.librarything.com/profile/lorax</url> * </person> * <factList> * <fact><![CDATA[ The Three Laws of Robotics * 1. A robot may not injure a human being, or, through inaction, allow a human being to come to harm. * 2. A robot must obey the orders given it by human beings except where such orders would conflict * with the First Law. * 3. A robot must protect its own existence as long as such protection does not conflict with the * First or Second Law. ]]> * </fact> * </factList> * </version> * </versionList> * </field> * <field type="30" name="dedication" displayName="Dedication"> * <versionList> * <version id="2503596" archived="0" lang="eng"> * <date timestamp="1274377341">Thu, 20 May 2010 13:42:21 -0400</date> * <person id="1797"> * <name>lorax</name> * <url>http://www.librarything.com/profile/lorax</url> * </person> * <factList> * <fact><![CDATA[ To John W. Campbell, Jr., who godfathered the robots ]]></fact> * </factList> * </version> * </versionList> * </field> * <field type="26" name="lastwords" displayName="Last words"> * <versionList> * <version id="2503594" archived="0" lang="eng"> * <date timestamp="1274377340">Thu, 20 May 2010 13:42:20 -0400</date> * <person id="1797"> * <name>lorax</name> * <url>http://www.librarything.com/profile/lorax</url> * </person> * <factList> * <fact><![CDATA[ Robbie:<br>"Well," said Mrs. Weston, at last, "I guess he can stay with us until he rusts." ]]></fact> * <fact><![CDATA[ Runaround:<br>"Space Station," said Donovan, "here I come." ]]></fact> * <fact><![CDATA[ Reason"<br>He grinned � and went into the ship. Muller would be here for several weeks � ]]></fact> * <fact><![CDATA[ Catch That Rabbit:<br>****<br>**** too spoilerish! ]]></fact> * <fact><![CDATA[ Liar:<br>"Liar!" ]]></fact> * <fact><![CDATA[ Little Lost Robot:<br>"� His very superiority caught him. Good-by General" ]]></fact> * <fact><![CDATA[ Escape:<br>To which Bogert added absently, "Strictly according to the contract, too." ]]></fact> * <fact><![CDATA[ Evidence:<br>Stephen Byerley chuckled. "I must reply that that is a somewhat farfetched idea."<br>The door closed behind her. ]]></fact> * <fact><![CDATA[ The Evitable Conflict:<br>And the fire behind the quartz went out and only a curl of smoke was left to indicate its place. ]]></fact> * <fact><![CDATA[ <i>She died last month at the age of eighty-two.</i> ]]></fact> * </factList> * </version> * </versionList> * </field> * <field type="25" name="firstwords" displayName="First words"> * <versionList> * <version id="2503593" archived="0" lang="eng"> * <date timestamp="1274377340">Thu, 20 May 2010 13:42:20 -0400</date> * <person id="1797"> * <name>lorax</name> * <url>http://www.librarything.com/profile/lorax</url> * </person> * <factList> * <fact><![CDATA[ Robbie:<br>"Ninety-eight � ninety-nine � <i>one hundred</i>." ]]></fact> * <fact><![CDATA[ Runaround:<br>It was one of Gregory Powell's favorite platitudes that nothing was to * be gained from excitement, so when Mike Donovan came leaping down the stairs toward him, red hair matted * with perspiration, Powell frowned. ]]> * </fact> * <fact><![CDATA[ Reason:<br>Half a year later, the boys had changed their minds. ]]></fact> * <fact><![CDATA[ Catch That Rabbit:<br>The vacation was longer than two weeks. ]]></fact> * <fact><![CDATA[ Liar!<br>Alfred Lanning lit his cigar carefully, but the tips of his fingers were trembling slightly. ]]></fact> * <fact><![CDATA[ Little Lost Robot:<br>When I did see Susan Calvin again, it was at the door of her office. ]]></fact> * <fact><![CDATA[ Escape!:<br>When Susan Calvin returned from Hyper Base, Alfred Lanning was waiting for her. ]]></fact> * <fact><![CDATA[ Evidence:<br>Francis Quinn was a politician of the new school. ]]></fact> * <fact><![CDATA[ The Evitable Conflict:<br>The Co-ordinator, in his private study, had that medieval curiosity, a fireplace. ]]></fact> * <fact><![CDATA[ <i>I looked at my notes and I didn't like them.</i> (Introduction) ]]></fact> * </factList> * </version> * </versionList> * </field> * <field type="21" name="canonicaltitle" displayName="Canonical title"> * <versionList> * <version id="2503590" archived="0" lang="eng"> * <date timestamp="1274377338">Thu, 20 May 2010 13:42:18 -0400</date> * <person id="1797"> * <name>lorax</name> * <url>http://www.librarything.com/profile/lorax</url> * </person> * <factList> * <fact>I, Robot</fact> * </factList> * </version> * </versionList> * </field> * <field type="3" name="characternames" displayName="People/Characters"> * <versionList> * <version id="2503589" archived="0" lang="eng"> * <date timestamp="1274377337">Thu, 20 May 2010 13:42:17 -0400</date> * <person id="1797"> * <name>lorax</name> * <url>http://www.librarything.com/profile/lorax</url> * </person> * <factList> * <fact>Susan Calvin</fact> * <fact>Cutie (QT1)</fact> * <fact>Gregory Powell</fact> * <fact>Mike Donovan</fact> * <fact>Robbie (RB-series)</fact> * <fact>Mr. Weston</fact> * <fact>Gloria Weston</fact> * <fact>Mrs. Weston</fact> * <fact>SPD-13 (Speedy)</fact> * <fact>Speedy (SPD-13)</fact> * <fact>QT1 (Cutie)</fact> * <fact>The Master</fact> * <fact>Prophet of the Master</fact> * <fact>Ren� Descartes</fact> * <fact>DV-5 (Dave)</fact><fact>Dave (DV-5)</fact> * <fact>HRB-34 (Herbie)</fact> * <fact>Herbie (HRB-34)</fact> * <fact>Gerald Black</fact> * <fact>NS-2 (Nestor)</fact> * <fact>Nestor (NS-2)</fact> * <fact>Peter Bogert</fact> * <fact>The Brain (computer)</fact> * <fact>Stephen Byerley</fact> * <fact>Francis Quinn</fact> * </factList> * </version> * </versionList> * </field> * <field type="2" name="placesmentioned" displayName="Important places"> * <versionList> * <version id="2503588" archived="0" lang="eng"> * <date timestamp="1274377336">Thu, 20 May 2010 13:42:16 -0400</date> * <person id="1797"> * <name>lorax</name> * <url>http://www.librarything.com/profile/lorax</url> * </person> * <factList> * <fact>Mercury</fact> * <fact>New York, New York, USA</fact> * <fact>Roosevelt Building</fact> * <fact>U.S. Robots and Mechanical Men factory</fact> * <fact>Hyper Base</fact> * </factList> * </version> * </versionList> * </field> * </fieldList> * </commonknowledge> * </item> * <legal>By using this data you agree to the LibraryThing API terms of service.</legal> * </ltml> * </response> * * A less well-known work produces rather less data: * * <?xml version="1.0" encoding="UTF-8"?> * <response stat="ok"> * <ltml xmlns="http://www.librarything.com/" version="1.1"> * <item id="255375" type="work"> * <author id="359458" authorcode="fallonmary">Mary Fallon</author> * <url>http://www.librarything.com/work/255375</url> * <commonknowledge/> * </item> * <legal>By using this data you agree to the LibraryThing API terms of service.</legal> * </ltml> * </response> * * but in both cases, in both cases it should be noted that the covers are still available. * */ public void searchByIsbn(String isbn, boolean fetchThumbnail, Bundle bookData) { String devKey = getDevKey(); if (devKey.equals("")) throw new RuntimeException("Developer Key not available"); // Base path for an ISBN search String path = String.format(DETAIL_URL, devKey, isbn); if (isbn.equals("")) throw new IllegalArgumentException(); URL url; // Setup the parser SAXParserFactory factory = SAXParserFactory.newInstance(); SAXParser parser; SearchLibraryThingEntryHandler entryHandler = new LibraryThingManager.SearchLibraryThingEntryHandler(bookData); try { url = new URL(path); parser = factory.newSAXParser(); // Make sure we follow LibraryThing ToS (no more than 1 request/second). waitUntilRequestAllowed(); parser.parse(Utils.getInputStream(url), entryHandler); // Dont bother catching general exceptions, they will be caught by the caller. } catch (MalformedURLException e) { String s = "unknown"; try { s = e.getMessage(); } catch (Exception e2) {}; Logger.logError(e, s); } catch (ParserConfigurationException e) { String s = "unknown"; try { s = e.getMessage(); } catch (Exception e2) {}; Logger.logError(e, s); } catch (SAXException e) { String s = e.getMessage(); // "unknown"; try { s = e.getMessage(); } catch (Exception e2) {}; Logger.logError(e, s); } catch (java.io.IOException e) { String s = "unknown"; try { s = e.getMessage(); } catch (Exception e2) {}; Logger.logError(e, s); } if (fetchThumbnail) getCoverImage(isbn, bookData, ImageSizes.LARGE); return; } /** * Parser Handler to collect the book data. * * @author Philip Warner */ private class SearchLibraryThingEntryHandler extends DefaultHandler { private Bundle mBookData = null; private StringBuilder mBuilder = new StringBuilder(); private FieldTypes mFieldType = FieldTypes.OTHER; SearchLibraryThingEntryHandler(Bundle bookData) { mBookData = bookData; } @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); mBuilder.append(ch, start, length); } /** * Add the current characters to the book collection if not already present. * * @param key Key for data to add */ private void addIfNotPresent(String key) { if (!mBookData.containsKey(key) || mBookData.getString(key).length() == 0) { mBookData.putString(key, mBuilder.toString()); } } /** * Add the current text data to the book collection if not present, otherwise * append the data as a list. * * @param key Key for data to add */ private void appendOrAdd(String key) { Utils.appendOrAdd(mBookData, key, mBuilder.toString()); } @Override public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { super.startElement(uri, localName, name, attributes); // reset the string. See note in endElement() for a discussion. mBuilder.setLength(0); if (localName.equalsIgnoreCase(RESPONSE)){ // Not really much to do; we *could* look for the <err> element, then report it. //String stat = attributes.getValue("", "stat"); } else if (localName.equalsIgnoreCase(ITEM)){ // We don't use it yet, but this contains the Work ID. LibraryThing supports // retrieval of other editions etc via the Work ID. String type = attributes.getValue("","type"); if (type != null && type.equalsIgnoreCase("work")) { //mWorkId = attributes.getValue("", "id"); } } else if (localName.equalsIgnoreCase(FIELD)){ // FIELDs are the main things we want. Once we are in a fieldm we wait for a FACT; these // are read in the endElement() method. String fieldName = attributes.getValue("", "name"); if (fieldName != null) { if (fieldName.equalsIgnoreCase(CANONICAL_TITLE)) { mFieldType = FieldTypes.TITLE; } else if (fieldName.equalsIgnoreCase(SERIES)) { mFieldType = FieldTypes.SERIES; } else if (fieldName.equalsIgnoreCase(PLACES)) { mFieldType = FieldTypes.PLACES; } else if (fieldName.equalsIgnoreCase(CHARACTERS)) { mFieldType = FieldTypes.CHARACTERS; } } } } @Override public void endElement(String uri, String localName, String name) throws SAXException { super.endElement(uri, localName, name); if (localName.equalsIgnoreCase(FIELD)){ // Reset the current field mFieldType = FieldTypes.NONE; } else if (localName.equalsIgnoreCase(AUTHOR)) { // Add the author Utils.appendOrAdd(mBookData, CatalogueDBAdapter.KEY_AUTHOR_DETAILS, mBuilder.toString()); } else if (localName.equalsIgnoreCase(FACT)) { // Process the FACT according to the active FIELD type. switch (mFieldType) { case TITLE: addIfNotPresent(CatalogueDBAdapter.KEY_TITLE); break; case SERIES: appendOrAdd(CatalogueDBAdapter.KEY_SERIES_DETAILS); break; case PLACES: appendOrAdd("__places"); break; case CHARACTERS: appendOrAdd("__characters"); break; } } // Note: // Always reset the length. This is not entirely the right thing to do, but works // because we always want strings from the lowest level (leaf) XML elements. // To be completely correct, we should maintain a stack of builders that are pushed and // popped as each startElement/endElement is called. But lets not be pedantic for now. mBuilder.setLength(0); } } /** * Get the cover image using the ISBN * * @param isbn */ public String getCoverImageUrl(String isbn, ImageSizes size) { String devKey = getDevKey(); if (devKey.equals("")) throw new RuntimeException("Developer Key not available"); String path = COVER_URL_SMALL; switch(size) { case SMALL: path = COVER_URL_SMALL; break; case MEDIUM: path = COVER_URL_MEDIUM; break; case LARGE: path = COVER_URL_LARGE; break; } // Get the 'large' version String url = String.format(path, devKey, isbn); return url; } /** * Get the cover image using the ISBN * * @param isbn */ public String getCoverImage(String isbn, Bundle bookData, ImageSizes size) { String url = getCoverImageUrl(isbn, size); //Log.e("BC", url + " " + isbn + " " + size.toString()); // Make sure we follow LibraryThing ToS (no more than 1 request/second). waitUntilRequestAllowed(); // Save it with an _LT suffix String filename = Utils.saveThumbnailFromUrl(url, "_LT_" + size + "_" + isbn); if (filename.length() > 0 && bookData != null) Utils.appendOrAdd(bookData, "__thumbnail", filename); return filename; } /** * Search for edition data. * * @param bookData * */ public static ArrayList<String> searchEditions(String isbn) { // Base path for an ISBN search String path = String.format(EDITIONS_URL, isbn); if (isbn.equals("")) throw new RuntimeException("Can not get editions without an ISBN"); ArrayList<String> editions = new ArrayList<String>(); // Setup the parser SAXParserFactory factory = SAXParserFactory.newInstance(); SearchLibraryThingEditionHandler entryHandler = new LibraryThingManager.SearchLibraryThingEditionHandler(editions); // Make sure we follow LibraryThing ToS (no more than 1 request/second). waitUntilRequestAllowed(); // Get it Utils.parseUrlOutput(path, factory, entryHandler); return editions; } /** * Parser Handler to collect the edition data. * * Typical request output: * * <?xml version="1.0" encoding="utf-8"?> * <idlist> * <isbn>0380014300</isbn> * <isbn>0839824270</isbn> * <isbn>0722194390</isbn> * <isbn>0783884257</isbn> * ...etc... * <isbn>2207301907</isbn> * </idlist> * @author Philip Warner */ static private class SearchLibraryThingEditionHandler extends DefaultHandler { private StringBuilder mBuilder = new StringBuilder(); private ArrayList<String> mEditions = new ArrayList<String>(); SearchLibraryThingEditionHandler(ArrayList<String> editions) { mEditions = editions; } @Override public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); mBuilder.append(ch, start, length); } @Override public void endElement(String uri, String localName, String name) throws SAXException { super.endElement(uri, localName, name); if (localName.equalsIgnoreCase(ISBN)){ // Add the isbn String isbn = mBuilder.toString(); mEditions.add(isbn); } // Note: // Always reset the length. This is not entirely the right thing to do, but works // because we always want strings from the lowest level (leaf) XML elements. // To be completely correct, we should maintain a stack of builders that are pushed and // popped as each startElement/endElement is called. But lets not be pedantic for now. mBuilder.setLength(0); } } public boolean isAvailable() { return getDevKey().length() > 0; } private String getDevKey() { SharedPreferences prefs = mAppContext.getSharedPreferences("bookCatalogue", android.content.Context.MODE_PRIVATE); String key = prefs.getString(LT_DEVKEY_PREF_NAME, ""); return key.replaceAll("[\\r\\t\\n\\s]*", ""); } }