/* * Copyright (c) 2013 Allogy Interactive. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.allogy.app.media; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.graphics.Color; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.text.TextPaint; import android.text.method.LinkMovementMethod; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.TranslateAnimation; import android.widget.LinearLayout; import android.widget.Toast; import com.allogy.app.R; import com.allogy.app.provider.Academic; import com.allogy.app.ui.LinkEnabledTextView; import com.allogy.app.ui.TextLinkClickListener; /** * Activity for Reading PDF, ePub, or other e-book formats * * @author pramod * */ public class EReaderActivity extends Activity implements TextLinkClickListener { private static final String TAG = EReaderActivity.class.getName(); // private static final String mFile = "/TestBig.txt"; private static String mFile = "/sdcard/dropbox/Getting Started.pdf"; // private static final String mFile = "/OnePage.txt"; // Used by other activities to send the information to this, through intent public static final String EXTRA_EBOOK_TYPE = "EReaderActivity.ebooktype"; public static final String EXTRA_FILE_URI = "EReaderActivity.uri"; public static final int TYPE_PDF = 0, TYPE_EPUB = 1, TYPE_PLAINTEXT = 2; /* * This class has all the information pertaining to all the contents to be * displayed. */ class PAGE { int pgNum; // Text to be displayed in the text view String Txt; public PAGE() { pgNum = -1; } public PAGE(int num) { pgNum = num; } protected void setText(String txt) { Txt = new String(txt); } } private static final String IMAGE_TAG_PREFIX = "allogyimageid:"; // "allogyimageid:xxxx" is the assumed format private static final int IMAGE_TAG_LENGTH = IMAGE_TAG_PREFIX.length(); // For handler @SuppressWarnings("unused") private static final int MSG_READING = 0, MSG_SUCCESS = 1, MSG_BUILDPAGES = 2, MSG_SHOWCURPAGE = 3, MSG_PAGESBUILT = 4; private static final String statUri[] = {"/sdcard/q2.bmp", "/sdcard/trig_book.PNG"}; // Animation objects private static Animation mLeftIn = null; private static Animation mRightIn = null; // Font sizes for small medium and large private static final float FONT_SMALL = 20f; private static final float FONT_MEDIUM = 24f; private static final float FONT_LARGE = 28f; private static final float FONT_DEFAULT = FONT_MEDIUM; private static final boolean USE_DECODER_API = false; // Display measurements // private static final int DEFAULT_WDTH = 320; // private static final int DEFAULT_HGHT = 480; private static int mLineHt; private static int mLinesPerPg; private static int mDispHght; private static int mDispWdth; private static TextPaint mPaint; // member variables start here private int mBookType; // Type of the book, pdf, epub etc., private Uri mBookUri; // Uri to the book got from the intent private EBook mEbook; // ebook object got from the decoders // The text is stored here. private String mFullText; // The entire text is stored here // Page number in the page objects which is currently being displayed. public int mCurrentPageNum; // The text is converted to lines based on font and stored in this. private String[] mTextLines; // The lines are converted to pages and stored in an array public ArrayList<PAGE> mPages; // Font Size options given to the user private final String[] FontSizes = {"Small", "Medium", "Large"}; private static float mFontSize; private float mFontSelected; // This is the view which displays the text and images private LinearLayout mReaderLayout; private LinkEnabledTextView mReaderText; // Used to show any progress in the activity private ProgressDialog mProgress; // This will go off once the decoder is implemented private BufferedReader mBuf; private String mReadString; // Used for debugging. Each variable enables some level in the print private static final boolean DBG_ENABLE_LVL0 = false; private static final boolean DBG_BUILDPAGE_PROC = false; private static final boolean DBG_PRINT_FILELINES = false; /* * Handler used for processing data and displaying the progress whenever user * changes any of the settings. */ private Handler mHandler = new Handler() { @Override public void handleMessage(android.os.Message msg) { super.handleMessage(msg); switch (msg.what) { case MSG_READING: // set a message text mProgress.setMessage("Reading file... Please Wait.."); mProgress.show(); break; case MSG_BUILDPAGES: // set a message text BldPgs bp = new BldPgs(); bp.execute(); mProgress.setMessage("Building Pages... Please Wait.."); mProgress.show(); break; case MSG_SHOWCURPAGE: refreshView(); mProgress.dismiss(); Toast.makeText( getBaseContext(), "Showing the current Page. " + "Process Running in the background", 5).show(); break; case MSG_PAGESBUILT: if (mProgress.isShowing()) mProgress.dismiss(); Toast.makeText(getBaseContext(), "Process Complete", 5).show(); break; } } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ereader); mReaderLayout = (LinearLayout) findViewById(R.id.ereader_layout); mReaderText = (LinkEnabledTextView) findViewById(R.id.ereader_textview); mReaderText.setOnTextLinkClickListener(this); mReaderText.setLinkTextColor(Color.GREEN); mReaderText.setMovementMethod(LinkMovementMethod.getInstance()); /* * Needs to be set to false so that the touches are not handled by the super * class */ mReaderText.setClickable(false); mReaderText.setLongClickable(false); mProgress = new ProgressDialog(this); mProgress.setCancelable(false); mProgress.setTitle("EBook reader"); mFontSize = FONT_DEFAULT; mReaderText.mActivity = this; mLeftIn = inFromLeftAnimation(); mRightIn = inFromRightAnimation(); /* * Set the font sizes of the text, Display heights and display widths */ mReaderText.setTextSize(FONT_DEFAULT); mDispHght = getWindowManager().getDefaultDisplay().getHeight(); mDispWdth = getWindowManager().getDefaultDisplay().getWidth(); setTextViewParams(); Intent passedIntent = getIntent(); if (passedIntent.hasExtra(EXTRA_EBOOK_TYPE)) { mBookType = passedIntent.getIntExtra(EXTRA_EBOOK_TYPE, -1); } else { Toast.makeText(this, "Could Not Open Book", Toast.LENGTH_SHORT).show(); this.finish(); } if (passedIntent.hasExtra(EXTRA_FILE_URI)) { mFile = passedIntent.getStringExtra(EXTRA_FILE_URI); Log.i(TAG, mFile); } else { Toast.makeText(this, "Could Not Open Book", Toast.LENGTH_SHORT).show(); this.finish(); } /* * The file will be read only once, once the text is available, Pages are * built whenever needed/user changes the settings */ if (!USE_DECODER_API) { try { File f = new File(mFile); if (f.exists()) { FileInputStream fileIS = new FileInputStream(f); mBuf = new BufferedReader(new InputStreamReader(fileIS)); mReadString = new String(); mFullText = new String(); Thread rFile = new Thread(new Runnable() { @Override public void run() { // just reading each line and pass it on the debugger try { mHandler.sendEmptyMessage(MSG_READING); while ((mReadString = mBuf.readLine()) != null) { if (DBG_PRINT_FILELINES) Log.d("line: ", mReadString); mFullText = mFullText.concat(mReadString + "\n"); } } catch (IOException e) { Log.i(TAG, "Exception:" + e.getMessage()); } mHandler.sendEmptyMessage(MSG_BUILDPAGES); } }); Log.i(TAG, "Start Text Doc Thread"); rFile.start(); } else { Log.i(TAG, "File not found!"); } } catch (FileNotFoundException e) { } } else { Thread fileParser = new Thread(new Runnable() { @Override public void run() { mHandler.sendEmptyMessage(MSG_READING); Cursor bookCursor = EReaderActivity.this.managedQuery(mBookUri, new String[] { Academic.Book._ID, Academic.Book.PATH}, null, null, Academic.Book.SORT_ORDER_DEFAULT); if (bookCursor == null) return; bookCursor.moveToFirst(); String path = bookCursor.getString(bookCursor .getColumnIndex(Academic.Book.PATH)); bookCursor.close(); Log.i("EReaderActivity", "Book Path: " + path); switch (mBookType) { case TYPE_EPUB: mEbook = new EPub(path); break; case TYPE_PDF: mEbook = new PDFBook(path); break; default: } ArrayList<String> Sections = mEbook.getSections(); mFullText = Sections.get(0); for (int i = 1; i < Sections.size(); i++) { Log.d("line: ", Sections.get(i)); mFullText += Sections.get(i); } mHandler.sendEmptyMessage(MSG_BUILDPAGES); } }); fileParser.start(); } } /* * This function sends a message to the handler to build the pages. It should * be ensured that the text is already available in the global variables * before calling this method. */ private void BuildPages() { setTextViewParams(); mHandler.sendEmptyMessage(MSG_BUILDPAGES); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.ereader_menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { boolean i = true; mFontSelected = mFontSize; // Handle item selection switch (item.getItemId()) { case R.id.font_size: if (i) { AlertDialog.Builder alt_bld = new AlertDialog.Builder(this); alt_bld.setIcon(R.drawable.fontsize); alt_bld.setTitle("Select the font size"); alt_bld.setPositiveButton("Done", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (mFontSelected != mFontSize) { mFontSize = mFontSelected; mReaderText.setTextSize(mFontSize); BuildPages(); } dialog.dismiss(); } }); alt_bld.setNegativeButton(getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alt_bld.setSingleChoiceItems(FontSizes, -1, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { String sz = FontSizes[item]; if (sz.equals("Small")) { mFontSelected = FONT_SMALL; } if (sz.equals("Medium")) { mFontSelected = FONT_MEDIUM; } if (sz.equals("Large")) { mFontSelected = FONT_LARGE; } } }); AlertDialog alert = alt_bld.create(); alert.show(); } default: return super.onOptionsItemSelected(item); } } /* * This method displays the currentpage. It get the current page number, gets * the corresponding page object and displays the text and images */ private void refreshView() { PAGE currPage; currPage = getPage(mCurrentPageNum); if (DBG_ENABLE_LVL0) Log.i("ref view", "Page Text: " + currPage.Txt); mReaderText.gatherLinksForText(currPage.Txt); } protected int stringLength(String str) { if (str == null) return 0; return (int) mPaint.measureText(str); } // Getting a page from the array private PAGE getPage(int pg) { if ((mPages.size() - 1) < pg) return mPages.get(mPages.size() - 1); return mPages.get(pg); } /* * This function returns the text beginning from currIdx till newline */ protected String getNextLine(String str, int currIdx) { String txt; if ((str.length() - 1) == currIdx) return null; int nl_idx = str.indexOf('\n', currIdx); if (nl_idx != -1) { // Make sure to include the newline character in the first string. txt = str.substring(currIdx, nl_idx + 1); } else { txt = str.substring(currIdx); } return txt; } /* * This function builds a single line from the text and returns the line */ protected String buildLine(String LineText, int MaxTextWidth) { String ln; int txtWidth; /* * Find the length of the text and keep running the loop until every word in * the line is used for building pages. */ txtWidth = stringLength(LineText); if (txtWidth < MaxTextWidth) return LineText; int bk = 0; int words_added = 0; boolean lastword = false; int spc_idx = LineText.indexOf(' '); if (spc_idx == -1) { /* * ix is the index of the newline. if there is no more spaces, it means * that there are no more words. Initialize the index to the newline and * break the loop once the last word is accessed. */ spc_idx = LineText.length(); } while (stringLength(LineText.substring(0, spc_idx)) < MaxTextWidth) { words_added++; bk = spc_idx; spc_idx = LineText.indexOf(" ", spc_idx + 1); if (spc_idx == -1) { if (lastword == true) break; spc_idx = LineText.length() - 1; lastword = true; } } if (words_added == 0) { /* * if a single word needs more than the line width, then break the word by * space. if a single word's width is greater than the width of the line * then split it by characters and display it */ if (stringLength(LineText.substring(0, spc_idx)) > MaxTextWidth) { String sb = LineText.substring(0, spc_idx); txtWidth = stringLength(sb); bk = 1; while (stringLength(sb.substring(0, bk)) < MaxTextWidth) bk++; bk--; } } // Save the line in the String Vector ln = LineText.substring(0, bk + 1); return ln; } /* * An AsyncTask to build pages from the text read. */ private class BldPgs extends AsyncTask<Void, Void, Boolean> { private boolean showpage = true; private int pg; /* For each page object */ private int LinesBuilt = 0; private boolean PageBuild = false; private int pgNum; /* Stores the page number that has been built */ private PAGE t = new PAGE(1); // The text that is used to build pages private String LineText; private String Line; // Maximum width that can be occupied by the text in the page // that is being built private int MaxTextWidth = mDispWdth; // Stores the index of newline character and spaces private int currIdx = 0; private int pg_idx = 0; // automatically done on worker thread (separate from UI thread) @Override protected Boolean doInBackground(Void... params) { mTextLines = new String[mLinesPerPg]; mPages = new ArrayList<PAGE>(); pgNum = 2; int temp = 0; pg = mCurrentPageNum; int TxtLngth = mFullText.length(); do { LineText = getNextLine(mFullText, currIdx); Line = buildLine(LineText, MaxTextWidth); Log.i("Background", "Iteration " + temp++ + " Idx " + currIdx + " Length is " + TxtLngth); addLine(Line); currIdx += Line.length(); } while (currIdx < (TxtLngth - 1)); BuildAddPageObject(); return true; } /* * adds the line to the array and builds the page if all the lines in the * page are built. */ protected void addLine(String line) { /* * Find the length of the text and keep running the loop until every word * in the line is used for building pages. */ mTextLines[LinesBuilt++] = Line; if (DBG_BUILDPAGE_PROC) { Log.i("doInBackground", "Lines Built : " + LinesBuilt); Log.i("doInBackground", "Current line : " + Line); } // Here the page object is built, added to the page arraylist and // displayed if it is the current page if (LinesBuilt == mLinesPerPg || PageBuild == true) { BuildAddPageObject(); } } protected void BuildAddPageObject() { String pgTxt = new String(); // Build the page here for (int i = 0; i < LinesBuilt; i++) { pgTxt += mTextLines[i]; } if (DBG_BUILDPAGE_PROC) { Log.i("doInBackground", "Page Number : " + t.pgNum); Log.i("doInBackground", "NumLines : " + LinesBuilt); Log.i("doInBackground", "NewPage Idx : " + pg_idx); Log.i("doInBackground", "Page Text : " + pgTxt); } // Put the text in the page object t.setText(pgTxt); MaxTextWidth = mDispWdth; mPages.add(t); // Reinitializations t = new PAGE(pgNum++); if ((showpage == false) && (mCurrentPageNum != pg)) { showpage = true; pg = mCurrentPageNum; } if ((showpage) && (pgNum - 2 >= pg)) { showpage = false; publishProgress(); } LinesBuilt = 0; } @Override protected void onProgressUpdate(Void... values) { if (DBG_BUILDPAGE_PROC) Log.i("onProgressUpdate", "Displaying the text now"); mHandler.sendEmptyMessage(MSG_SHOWCURPAGE); } // When this is called show a message that the page build process is // complete @Override protected void onPostExecute(Boolean result) { if (result) { if (DBG_BUILDPAGE_PROC) Log.i("Build Pages Async Task", "Post execute"); mHandler.sendEmptyMessage(MSG_PAGESBUILT); } } } public void showNextPage() { int curPage = mCurrentPageNum; if (curPage == (mPages.size() - 1)) { Toast.makeText(getBaseContext(), "Displaying the last page", Toast.LENGTH_SHORT).show(); return; } mCurrentPageNum = (curPage + 1); mReaderLayout.startAnimation(mRightIn); refreshView(); } public void showPreviousPage() { int curPage = mCurrentPageNum; if (curPage == 0) { Toast.makeText(getBaseContext(), "Displaying the 1st page", Toast.LENGTH_SHORT).show(); return; } mCurrentPageNum = (curPage - 1); mReaderLayout.startAnimation(mLeftIn); refreshView(); } private Animation inFromRightAnimation() { Animation inFromRight = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.9f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f); inFromRight.setDuration(300); inFromRight.setInterpolator(new DecelerateInterpolator()); return inFromRight; } private Animation inFromLeftAnimation() { Animation inFromLeft = new TranslateAnimation(Animation.RELATIVE_TO_PARENT, -0.6f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f); inFromLeft.setDuration(300); inFromLeft.setInterpolator(new DecelerateInterpolator()); return inFromLeft; } private void setTextViewParams() { mLineHt = mReaderText.getLineHeight(); mLinesPerPg = (int) Math.floor(mDispHght / mLineHt); mPaint = mReaderText.getPaint(); } public void onTextLinkClick(View textView, String clickedString) { int id = Integer.parseInt(clickedString.substring(IMAGE_TAG_LENGTH)); Intent intent = new Intent(); intent.setAction(android.content.Intent.ACTION_VIEW); Log.i("TextLinkClick", "id " + id); File file = new File(statUri[id - 1]); intent.setDataAndType(Uri.fromFile(file), "image/*"); startActivity(intent); } @SuppressWarnings("unused") private void printinfo(String Tag) { Log.i(Tag, " Num lines " + mReaderText.getLineCount()); Log.i(Tag, " Line Height " + mReaderText.getLineHeight()); Log.i(Tag, " Height " + mReaderText.getHeight()); Log.i(Tag, " Width " + mReaderText.getWidth()); } }