Beispiel #1
0
public class DocumentModel extends ListenerProxy {

  protected static final LogContext LCTX = LogContext.ROOT.lctx("DocModel");

  protected PageIndex currentIndex = PageIndex.FIRST;

  private static final Page[] EMPTY_PAGES = {};

  private final CodecContext context;
  private final DecodeService decodeService;

  private Page[] pages = EMPTY_PAGES;

  public DocumentModel(final CodecType activityType) {
    super(CurrentPageListener.class);
    if (activityType != null) {
      try {
        context = activityType.getContextClass().newInstance();
        decodeService = new DecodeServiceBase(context);
      } catch (final Throwable th) {
        throw new RuntimeException(th);
      }
    } else {
      context = null;
      decodeService = new DecodeServiceStub();
    }
  }

  public void open(String fileName, String password) {
    decodeService.open(fileName, password);
  }

  public DecodeService getDecodeService() {
    return decodeService;
  }

  public Page[] getPages() {
    return pages;
  }

  public Iterable<Page> getPages(final int start) {
    return new PageIterator(start, pages.length);
  }

  public Iterable<Page> getPages(final int start, final int end) {
    return new PageIterator(start, Math.min(end, pages.length));
  }

  public int getPageCount() {
    return LengthUtils.length(pages);
  }

  public void recycle() {
    decodeService.recycle();
    recyclePages();
  }

  private void recyclePages() {
    if (LengthUtils.isNotEmpty(pages)) {
      final List<Bitmaps> bitmapsToRecycle = new ArrayList<Bitmaps>();
      for (final Page page : pages) {
        page.recycle(bitmapsToRecycle);
      }
      BitmapManager.release(bitmapsToRecycle);
      BitmapManager.release();
    }
    pages = EMPTY_PAGES;
  }

  public Page getPageObject(final int viewIndex) {
    return pages != null && 0 <= viewIndex && viewIndex < pages.length ? pages[viewIndex] : null;
  }

  public Page getPageByDocIndex(final int docIndex) {
    for (Page page : pages) {
      if (page.index.docIndex == docIndex) {
        return page;
      }
    }
    return null;
  }

  /**
   * Gets the current page object.
   *
   * @return the current page object
   */
  public Page getCurrentPageObject() {
    return getPageObject(this.currentIndex.viewIndex);
  }

  /**
   * Gets the last page object.
   *
   * @return the last page object
   */
  public Page getLastPageObject() {
    return getPageObject(pages.length - 1);
  }

  public void setCurrentPageIndex(final PageIndex newIndex) {
    if (!CompareUtils.equals(currentIndex, newIndex)) {
      if (LCTX.isDebugEnabled()) {
        LCTX.d("Current page changed: " + "currentIndex" + " -> " + newIndex);
      }

      final PageIndex oldIndex = this.currentIndex;
      this.currentIndex = newIndex;

      this.<CurrentPageListener>getListener().currentPageChanged(oldIndex, newIndex);
    }
  }

  public PageIndex getCurrentIndex() {
    return this.currentIndex;
  }

  public int getCurrentViewPageIndex() {
    return this.currentIndex.viewIndex;
  }

  public int getCurrentDocPageIndex() {
    return this.currentIndex.docIndex;
  }

  public void setCurrentPageByFirstVisible(final int firstVisiblePage) {
    final Page page = getPageObject(firstVisiblePage);
    if (page != null) {
      setCurrentPageIndex(page.index);
    }
  }

  public void initPages(
      final IActivityController base, final IActivityController.IBookLoadTask task) {
    recyclePages();

    final BookSettings bs = SettingsManager.getBookSettings();

    if (base == null || bs == null || context == null || decodeService == null) {
      return;
    }

    final IView view = base.getView();

    final CodecPageInfo defCpi = new CodecPageInfo();
    defCpi.width = (view.getWidth());
    defCpi.height = (view.getHeight());

    int viewIndex = 0;

    final long start = System.currentTimeMillis();
    try {
      final ArrayList<Page> list = new ArrayList<Page>();
      final CodecPageInfo[] infos = retrievePagesInfo(base, bs, task);

      for (int docIndex = 0; docIndex < infos.length; docIndex++) {
        if (!bs.splitPages
            || infos[docIndex] == null
            || (infos[docIndex].width < infos[docIndex].height)) {
          final Page page =
              new Page(
                  base,
                  new PageIndex(docIndex, viewIndex++),
                  PageType.FULL_PAGE,
                  infos[docIndex] != null ? infos[docIndex] : defCpi);
          list.add(page);
        } else {
          final Page page1 =
              new Page(
                  base, new PageIndex(docIndex, viewIndex++), PageType.LEFT_PAGE, infos[docIndex]);
          list.add(page1);
          final Page page2 =
              new Page(
                  base, new PageIndex(docIndex, viewIndex++), PageType.RIGHT_PAGE, infos[docIndex]);
          list.add(page2);
        }
      }
      pages = list.toArray(new Page[list.size()]);
      if (pages.length > 0) {
        createBookThumbnail(bs, pages[0], false);
      }
    } finally {
      LCTX.d("Loading page info: " + (System.currentTimeMillis() - start) + " ms");
    }
  }

  public void createBookThumbnail(final BookSettings bs, final Page page, final boolean override) {
    final ThumbnailFile thumbnailFile = CacheManager.getThumbnailFile(bs.fileName);
    if (!override && thumbnailFile.exists()) {
      return;
    }

    int width = 200, height = 200;
    final RectF bounds = page.getBounds(1.0f);
    final float pageWidth = bounds.width();
    final float pageHeight = bounds.height();

    if (pageHeight > pageWidth) {
      width = (int) (200 * pageWidth / pageHeight);
    } else {
      height = (int) (200 * pageHeight / pageWidth);
    }

    BitmapRef image =
        decodeService.createThumbnail(
            width, height, page.index.docIndex, page.type.getInitialRect());
    thumbnailFile.setImage(image != null ? image.getBitmap() : null);
    BitmapManager.release(image);
  }

  private CodecPageInfo[] retrievePagesInfo(
      final IActivityController base,
      final BookSettings bs,
      final IActivityController.IBookLoadTask task) {
    final PageCacheFile pagesFile = CacheManager.getPageFile(bs.fileName);

    if (decodeService.isPageSizeCacheable() && pagesFile.exists()) {
      final CodecPageInfo[] infos = pagesFile.load();
      if (infos != null) {
        return infos;
      }
    }

    final CodecPageInfo[] infos = new CodecPageInfo[getDecodeService().getPageCount()];
    final CodecPageInfo unified = decodeService.getUnifiedPageInfo();
    for (int i = 0; i < infos.length; i++) {
      if (task != null) {
        task.setProgressDialogMessage(R.string.msg_getting_page_size, (i + 1), infos.length);
      }
      infos[i] = unified != null ? unified : getDecodeService().getPageInfo(i);
    }

    if (decodeService.isPageSizeCacheable()) {
      pagesFile.save(infos);
    }
    return infos;
  }

  private final class PageIterator implements Iterable<Page>, Iterator<Page> {

    private final int end;
    private int index;

    private PageIterator(final int start, final int end) {
      this.index = start;
      this.end = end;
    }

    @Override
    public boolean hasNext() {
      return 0 <= index && index < end;
    }

    @Override
    public Page next() {
      return hasNext() ? pages[index++] : null;
    }

    @Override
    public void remove() {}

    @Override
    public Iterator<Page> iterator() {
      return this;
    }
  }
}
Beispiel #2
0
public class DrawThread extends Thread {

  private static final LogContext LCTX = LogContext.ROOT.lctx("Imaging");

  private final SurfaceHolder surfaceHolder;

  private final BlockingQueue<ViewState> queue = new ArrayBlockingQueue<ViewState>(16, true);

  private final Flag stop = new Flag();

  public DrawThread(final SurfaceHolder surfaceHolder) {
    this.surfaceHolder = surfaceHolder;
  }

  public void finish() {
    stop.set();
    try {
      this.join();
    } catch (final InterruptedException e) {
    }
  }

  @Override
  public void run() {
    while (!stop.get()) {
      draw(false);
    }
  }

  protected void draw(final boolean useLastState) {
    final ViewState viewState = takeTask(1, TimeUnit.SECONDS, useLastState);
    if (viewState == null) {
      return;
    }
    Canvas canvas = null;
    try {
      canvas = surfaceHolder.lockCanvas(null);
      EventPool.newEventDraw(viewState, canvas).process();
    } catch (final Throwable th) {
      LCTX.e("Unexpected error on drawing: " + th.getMessage(), th);
    } finally {
      if (canvas != null) {
        surfaceHolder.unlockCanvasAndPost(canvas);
      }
    }
  }

  public ViewState takeTask(final long timeout, final TimeUnit unit, final boolean useLastState) {
    ViewState task = null;
    try {
      task = queue.poll(timeout, unit);
      if (task != null && useLastState) {
        final ArrayList<ViewState> list = new ArrayList<ViewState>();
        // Workaround for possible ConcurrentModificationException
        while (true) {
          list.clear();
          try {
            if (queue.drainTo(list) > 0) {
              task = list.get(list.size() - 1);
            }
            break;
          } catch (Throwable ex) {
            // Go to next attempt
            LCTX.e(
                "Unexpected error on retrieving last view state from draw queue: "
                    + ex.getMessage());
          }
        }
      }
    } catch (final InterruptedException e) {
      Thread.interrupted();
    } catch (Throwable ex) {
      // Go to next attempt
      LCTX.e("Unexpected error on retrieving view state from draw queue: " + ex.getMessage());
    }
    return task;
  }

  public void draw(final ViewState viewState) {
    if (viewState != null) {
      // Workaround for possible ConcurrentModificationException
      while (true) {
        try {
          queue.offer(viewState);
          break;
        } catch (Throwable ex) {
          // Go to next attempt
          LCTX.e("Unexpected error on adding view state to draw queue: " + ex.getMessage());
        }
      }
    }
  }
}
Beispiel #3
0
public class TouchManager {

  private static final LogContext LCTX = LogContext.ROOT.lctx("Actions");

  public static final String DEFAULT_PROFILE = "DocumentView.Default";

  private static final Map<String, TouchProfile> profiles =
      new HashMap<String, TouchManager.TouchProfile>();

  private static final LinkedList<TouchProfile> stack = new LinkedList<TouchProfile>();

  public static void loadFromSettings(final AppSettings newSettings) {
    profiles.clear();
    stack.clear();

    boolean fromJSON = false;
    final String str = newSettings.tapProfiles;
    if (LengthUtils.isNotEmpty(str)) {
      try {
        final List<TouchProfile> list = fromJSON(str);
        for (final TouchProfile p : list) {
          profiles.put(p.name, p);
        }
      } catch (final Throwable ex) {
        LCTX.e("Error on tap configuration load: ", ex);
      }
      fromJSON = profiles.containsKey(DEFAULT_PROFILE);
    }

    if (!fromJSON) {
      if (LCTX.isDebugEnabled()) {
        LCTX.d("Creating default tap configuration...");
      }
      final TouchProfile def = addProfile(DEFAULT_PROFILE);
      {
        final Region r = def.addRegion(0, 0, 100, 100);
        r.setAction(Touch.DoubleTap, R.id.actions_openOptionsMenu, true);
      }
      {
        final Region r = def.addRegion(0, 0, 100, 10);
        r.setAction(Touch.SingleTap, R.id.actions_verticalConfigScrollUp, true);
      }
      {
        final Region r = def.addRegion(0, 90, 100, 100);
        r.setAction(Touch.SingleTap, R.id.actions_verticalConfigScrollDown, true);
      }

      persist();
    }

    stack.addFirst(profiles.get(DEFAULT_PROFILE));
  }

  public static void persist() {
    try {
      final JSONObject json = toJSON();
      AppSettings.updateTapProfiles(json.toString());
    } catch (final JSONException ex) {
      ex.printStackTrace();
    }
  }

  public static void setActionEnabled(final String profile, final int id, final boolean enabled) {
    final TouchProfile tp = profiles.get(profile);
    for (final Region r : tp.regions) {
      for (final ActionRef a : r.actions) {
        if (a != null && a.id == id) {
          a.enabled = enabled;
        }
      }
    }
  }

  public static void setActionEnabled(
      final String profile,
      final int id,
      final boolean enabled,
      final int left,
      final int top,
      final int right,
      final int bottom) {
    final TouchProfile tp = profiles.get(profile);
    for (final Region r : tp.regions) {
      for (final ActionRef a : r.actions) {
        if (a != null && a.id == id) {
          a.enabled = enabled;
          r.rect.left = left;
          r.rect.top = top;
          r.rect.right = right;
          r.rect.bottom = bottom;
          return;
        }
      }
    }
  }

  public static Integer getAction(
      final Touch type, final float x, final float y, final float width, final float height) {
    return AppSettings.current().tapsEnabled
        ? stack.peek().getAction(type, x, y, width, height)
        : null;
  }

  public static TouchProfile addProfile(final String name) {
    final TouchProfile tp = new TouchProfile(name);
    profiles.put(tp.name, tp);
    return tp;
  }

  public static TouchProfile topProfile() {
    return stack.isEmpty() ? null : stack.peek();
  }

  public static TouchProfile pushProfile(final String name) {
    final TouchProfile prev = stack.isEmpty() ? null : stack.peek();
    final TouchProfile tp = profiles.get(name);
    if (tp != null) {
      stack.addFirst(tp);
    }
    return prev;
  }

  public static TouchProfile popProfile() {
    if (stack.size() > 1) {
      stack.removeFirst();
    }
    return stack.peek();
  }

  public static JSONObject toJSON() throws JSONException {
    final JSONObject object = new JSONObject();
    final JSONArray array = new JSONArray();
    for (final TouchProfile p : profiles.values()) {
      array.put(p.toJSON());
    }
    object.put("profiles", array);
    return object;
  }

  private static List<TouchProfile> fromJSON(final String str) throws JSONException {
    final List<TouchProfile> list = new ArrayList<TouchProfile>();

    final JSONObject root = new JSONObject(str);

    final JSONArray profiles = root.getJSONArray("profiles");
    for (int pIndex = 0; pIndex < profiles.length(); pIndex++) {
      final JSONObject p = profiles.getJSONObject(pIndex);
      final TouchProfile profile = TouchProfile.fromJSON(p);
      list.add(profile);
    }
    return list;
  }

  public static class TouchProfile {

    public final String name;
    final LinkedList<Region> regions = new LinkedList<Region>();

    public TouchProfile(final String name) {
      super();
      this.name = name;
    }

    public ListIterator<Region> regions() {
      return regions.listIterator();
    }

    public void clear() {
      regions.clear();
    }

    public Integer getAction(
        final Touch type, final float x, final float y, final float width, final float height) {
      LCTX.d("getAction(" + type + ", " + x + ", " + y + ", " + width + ", " + height + ")");
      for (final Region r : regions) {
        final RectF rect = r.getActualRect(width, height);
        LCTX.d("Region: " + rect);
        if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
          final ActionRef action = r.getAction(type);
          LCTX.d("Action: " + action);
          if (action != null && action.enabled) {
            return action.id;
          }
        }
      }
      return null;
    }

    public Region getRegion(final float x, final float y, final float width, final float height) {
      LCTX.d("getRegion(" + x + ", " + y + ", " + width + ", " + height + ")");
      for (final Region r : regions) {
        final RectF rect = r.getActualRect(width, height);
        LCTX.d("Region: " + rect);
        if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {
          return r;
        }
      }
      return null;
    }

    public Region addRegion(final int left, final int top, final int right, final int bottom) {
      final Region r = new Region(new Rect(left, top, right, bottom));
      return addRegion(r);
    }

    public Region addRegion(final Region r) {
      regions.addFirst(r);
      return r;
    }

    public void removeRegion(final Region r) {
      regions.remove(r);
    }

    @Override
    public String toString() {
      final StringBuilder buf = new StringBuilder(this.getClass().getSimpleName());
      buf.append("[");
      buf.append("name").append("=").append(name);
      buf.append(", ");
      buf.append("regions").append("=").append(regions);
      buf.append("]");

      return buf.toString();
    }

    public JSONObject toJSON() throws JSONException {
      final JSONObject object = new JSONObject();
      object.put("name", this.name);

      final JSONArray array = new JSONArray();
      for (final Region r : regions) {
        array.put(r.toJSON());
      }
      object.put("regions", array);

      return object;
    }

    public static TouchProfile fromJSON(final JSONObject json) throws JSONException {
      final TouchProfile profile = new TouchProfile(json.getString("name"));

      final JSONArray regions = json.getJSONArray("regions");
      for (int rIndex = 0; rIndex < regions.length(); rIndex++) {
        final JSONObject r = regions.getJSONObject(rIndex);
        final Region region = Region.fromJSON(r);
        profile.regions.add(region);
      }
      return profile;
    }
  }

  public static enum Touch {
    SingleTap,
    DoubleTap,
    LongTap,
    TwoFingerTap;
  }

  public static class Region {

    private final Rect rect;
    private final ActionRef[] actions = new ActionRef[Touch.values().length];

    public Region(final Rect r) {
      rect = r;
    }

    public Region(final Region r) {
      this.rect = new Rect(r.rect);
      for (int i = 0; i < actions.length; i++) {
        this.actions[i] = r.actions[i];
      }
    }

    public Rect getRect() {
      return rect;
    }

    public ActionRef getAction(final Touch type) {
      return actions[type.ordinal()];
    }

    public ActionRef setAction(final Touch type, final int id, final boolean enabled) {
      final ActionRef a = new ActionRef(type, id, enabled);
      actions[type.ordinal()] = a;
      return a;
    }

    public RectF getActualRect(final float width, final float height) {
      return new RectF(
          width * rect.left / 100.0f,
          height * rect.top / 100.0f,
          width * rect.right / 100.0f,
          height * rect.bottom / 100.0f);
    }

    public void clear(Touch type) {
      this.actions[type.ordinal()] = null;
    }

    public void clear() {
      for (int i = 0; i < actions.length; i++) {
        this.actions[i] = null;
      }
    }

    @Override
    public String toString() {
      final StringBuilder buf = new StringBuilder(this.getClass().getSimpleName());
      buf.append("[");
      buf.append("rect").append("=").append(rect);
      buf.append(", ");
      buf.append("actions").append("=").append(Arrays.toString(actions));
      buf.append("]");

      return buf.toString();
    }

    public JSONObject toJSON() throws JSONException {
      final JSONObject object = new JSONObject();

      final JSONObject r = new JSONObject();
      r.put("left", rect.left);
      r.put("top", rect.top);
      r.put("right", rect.right);
      r.put("bottom", rect.bottom);
      object.put("rect", r);

      final JSONArray a = new JSONArray();
      for (final ActionRef action : actions) {
        if (action != null) {
          a.put(action.toJSON());
        }
      }
      object.put("actions", a);
      return object;
    }

    public static Region fromJSON(final JSONObject json) throws JSONException {
      final JSONObject r = json.getJSONObject("rect");
      final Rect rect =
          new Rect(r.getInt("left"), r.getInt("top"), r.getInt("right"), r.getInt("bottom"));

      final Region region = new Region(rect);
      final JSONArray actions = json.getJSONArray("actions");
      for (int aIndex = 0; aIndex < actions.length(); aIndex++) {
        try {
          final JSONObject a = actions.getJSONObject(aIndex);
          final Touch type = Touch.valueOf(a.getString("type"));
          final String name = a.getString("name");
          final Integer id = ActionEx.getActionId(name);
          if (id != null) {
            region.setAction(type, id, a.getBoolean("enabled"));
          } else {
            LCTX.e("Unknown action name: " + name);
          }
        } catch (final JSONException ex) {
          throw new JSONException(
              "Old perssitent format found. Touch action are returned to default ones: "
                  + ex.getMessage());
        }
      }
      return region;
    }
  }

  public static class ActionRef {

    public final Touch type;
    public final int id;
    public final String name;
    public boolean enabled;

    public ActionRef(final Touch type, final int id, final boolean enabled) {
      this.type = type;
      this.id = id;
      this.name = ActionEx.getActionName(id);
      this.enabled = enabled;
    }

    public JSONObject toJSON() throws JSONException {
      final JSONObject object = new JSONObject();
      object.put("type", type.name());
      object.put("name", name);
      object.put("enabled", enabled);
      return object;
    }

    @Override
    public String toString() {
      return "(" + type + ", " + name + ", " + enabled + ")";
    }
  }
}