Example #1
0
public interface IUIManager {

  LogContext LCTX = LogManager.root().lctx("UIManager");

  IUIManager instance =
      /* Check old versions */
      AndroidVersion.lessThan3x
          /* UIManager1x */
          ? new UIManager1x()
          /* Check Android 3.x versions */
          : AndroidVersion.is3x
              /* UIManager3x */
              ? new UIManager3x()
              /* Check Android 4.0.x versions */
              : AndroidVersion.is40x
                  /* UIManager40x */
                  ? new UIManager40x()
                  /* UIManager41x */
                  : new UIManager41x();

  void onPause(Activity activity);

  void onResume(Activity activity);

  void onDestroy(Activity activity);

  void setFullScreenMode(Activity activity, View view, boolean fullScreen);

  void setTitleVisible(Activity activity, boolean visible, boolean firstTime);

  boolean isTitleVisible(Activity activity);

  void openOptionsMenu(final Activity activity, final View view);

  void invalidateOptionsMenu(final Activity activity);

  void onMenuOpened(Activity activity);

  void onMenuClosed(Activity activity);

  boolean isTabletUi(final Activity activity);
}
public class EBookDroidLibraryLoader {

  private static final LogContext LCTX = LogManager.root().lctx("LibraryLoader");

  private static boolean alreadyLoaded = false;

  public static void load() {
    if (alreadyLoaded) {
      return;
    }
    try {
      System.loadLibrary("ebookdroid");
      alreadyLoaded = true;
    } catch (Throwable th) {
      LCTX.e("Native library cannot be loaded: ", th);
      throw new RuntimeException(th);
    }
  }

  public static native void free();
}
public class TouchManagerView extends View {

  private static final LogContext LCTX = LogManager.root().lctx("TouchManagerView");

  private static final float GRID_X = 10;
  private static final float GRID_Y = 10;

  private static final int[] COLORS = {Color.BLUE, Color.GRAY, Color.RED, Color.YELLOW};

  private final IActivityController base;

  private final Paint bgPaint;
  private final Paint gridPaint;
  private final ActionController<TouchManagerView> actions;
  private final DefaultGestureDetector detector;

  private TouchProfile profile;

  private final Paint rPaint = new Paint();

  public TouchManagerView(final IActivityController base) {
    super(base.getContext());
    this.base = base;
    this.actions = new ActionController<TouchManagerView>(base, this);
    this.detector = new DefaultGestureDetector(getContext(), new GestureListener());

    super.setVisibility(View.GONE);

    setFocusable(true);
    setFocusableInTouchMode(true);

    bgPaint = new Paint();
    bgPaint.setColor(Color.BLACK);
    bgPaint.setStyle(Paint.Style.FILL);
    bgPaint.setAlpha(0x40);

    gridPaint = new Paint();
    gridPaint.setColor(Color.GREEN);
  }

  @Override
  public void setVisibility(final int visibility) {
    if (visibility == View.VISIBLE) {
      profile = TouchManager.topProfile();
    } else {
      profile = null;
    }
    super.setVisibility(visibility);
  }

  @Override
  protected void onDraw(final Canvas canvas) {
    super.onDraw(canvas);
    if (profile == null) {
      return;
    }

    final int width = getWidth();
    final int height = getHeight();

    canvas.drawRect(0, 0, width, height, bgPaint);

    final float xStep = width / GRID_X;
    final float yStep = height / GRID_Y;

    int cIndex = 0;
    ListIterator<Region> regions = profile.regions(false);
    while (regions.hasPrevious()) {
      final Region region = regions.previous();
      final RectF r = region.getActualRect(width, height);

      rPaint.setColor(COLORS[cIndex]);
      rPaint.setAlpha(0x80);
      canvas.drawRect(r, rPaint);

      cIndex = (cIndex + 1) % COLORS.length;
    }

    drawGrid(canvas, xStep, yStep);

    cIndex = 0;
    regions = profile.regions(false);
    while (regions.hasPrevious()) {
      final Region region = regions.previous();
      final RectF r = region.getActualRect(width, height);
      rPaint.setColor(COLORS[cIndex]);
      drawBounds(canvas, r, rPaint);

      cIndex = (cIndex + 1) % COLORS.length;
    }

    if (current != null) {
      rPaint.setColor(Color.WHITE);
      rPaint.setAlpha(0x80);
      final RectF r = current.getActualRect(width, height);
      canvas.drawRect(r, rPaint);
      rPaint.setColor(Color.WHITE);
      drawBounds(canvas, r, rPaint);
    }
  }

  protected void drawGrid(final Canvas canvas, final float xStep, final float yStep) {
    for (float x = xStep; x < getWidth(); x += xStep) {
      canvas.drawLine(x, 0, x, getHeight(), gridPaint);
    }

    for (float y = yStep; y < getHeight(); y += yStep) {
      canvas.drawLine(0, y, getWidth(), y, gridPaint);
    }
  }

  protected void drawBounds(final Canvas canvas, final RectF r, final Paint p) {
    canvas.drawLine(r.left, r.top, r.right - 1, r.top, p);
    canvas.drawLine(r.left, r.bottom - 1, r.right - 1, r.bottom - 1, p);
    canvas.drawLine(r.left, r.top, r.left, r.bottom - 1, p);
    canvas.drawLine(r.right - 1, r.top, r.right - 1, r.bottom - 1, p);
  }

  private PointF startPoint;
  private PointF endPoint;
  private Region current;

  protected void processRegion() {
    if (profile != null) {
      if (startPoint != null && endPoint != null) {
        current = getOrCreareRegion(startPoint, endPoint);
      }
      if (LCTX.isDebugEnabled()) {
        LCTX.d("processRegion(): " + current);
      }
      if (current != null) {
        final TouchConfigDialog dlg = new TouchConfigDialog(base, this, profile, current);
        dlg.show();
      }
    }

    startPoint = null;
    endPoint = null;
    current = null;
  }

  protected Region getOrCreareRegion(final PointF startPoint, final PointF endPoint) {
    final Region selected = getRegion(startPoint, endPoint);
    for (final Region r : profile.regions) {
      if (r.getRect().equals(selected.getRect())) {
        return r;
      }
    }
    profile.addRegion(selected);
    return selected;
  }

  @Override
  public final boolean onTouchEvent(final MotionEvent ev) {
    try {
      Thread.sleep(16);
    } catch (final InterruptedException e) {
      Thread.interrupted();
    }

    if (LCTX.isDebugEnabled()) {
      LCTX.d("onTouchEvent(): " + ev);
    }
    boolean res = detector.onTouchEvent(ev);

    int action = ev.getAction();
    if (!res && (action == MotionEvent.ACTION_UP)) {
      if (startPoint != null) {
        endPoint = new PointF(ev.getX(), ev.getY());
        current = getRegion(startPoint, endPoint);
        processRegion();
      }
      invalidate();
      return true;
    }
    return res;
  }

  protected Region getRegion(final PointF startPoint, final PointF endPoint) {
    final float width = getWidth();
    final float height = getHeight();
    final float xStep = width / GRID_X;
    final float yStep = height / GRID_Y;

    final float cellWidth = 100 / GRID_X;
    final float cellHeight = 100 / GRID_X;

    float left = MathUtils.fmin(startPoint.x, endPoint.x);
    float right = MathUtils.fmax(startPoint.x, endPoint.x);
    float top = MathUtils.fmin(startPoint.y, endPoint.y);
    float bottom = MathUtils.fmax(startPoint.y, endPoint.y);

    left = cellWidth * FloatMath.floor(left / xStep);
    right = cellWidth * FloatMath.floor(right / xStep) + cellWidth;
    top = cellHeight * FloatMath.floor(top / yStep);
    bottom = cellHeight * FloatMath.floor(bottom / yStep) + cellHeight;

    return new Region(MathUtils.rect(left, top, right, bottom));
  }

  protected class GestureListener extends SimpleOnGestureListener {

    @Override
    public boolean onDoubleTap(final MotionEvent e) {
      actions.getOrCreateAction(R.id.actions_toggleTouchManagerView).run();
      return true;
    }

    @Override
    public boolean onDown(final MotionEvent e) {
      startPoint = new PointF(e.getX(), e.getY());
      endPoint = startPoint;
      current = getRegion(startPoint, endPoint);
      if (LCTX.isDebugEnabled()) {
        LCTX.d("onDown(): " + current);
      }
      return true;
    }

    @Override
    public boolean onSingleTapUp(MotionEvent e) {
      if (LCTX.isDebugEnabled()) {
        LCTX.d("onSingleTapUp(): " + current);
      }
      endPoint = null;
      return true;
    }

    @Override
    public boolean onScroll(
        final MotionEvent e1, final MotionEvent e2, final float distanceX, final float distanceY) {
      final float x = e2.getX(), y = e2.getY();
      endPoint = new PointF(x, y);
      current = getRegion(startPoint, endPoint);
      if (LCTX.isDebugEnabled()) {
        LCTX.d("onScroll(): " + current);
      }
      invalidate();
      return true;
    }

    @Override
    public boolean onSingleTapConfirmed(final MotionEvent e) {
      if (profile != null) {
        current = profile.getRegion(e.getX(), e.getY(), getWidth(), getHeight());
        if (LCTX.isDebugEnabled()) {
          LCTX.d("onSingleTapConfirmed(): " + current);
        }
        processRegion();
      }
      return true;
    }

    @Override
    public void onLongPress(final MotionEvent e) {}
  }
}
public class FileSystemScanner {

  private static final LogContext LCTX = LogManager.root().lctx("FileSystemScanner", false);

  private static final int EVENT_MASK = CLOSE_WRITE | MOVED_TO | DELETE | MOVED_FROM;

  final EventDispatcher listeners;
  final AtomicBoolean inScan = new AtomicBoolean();
  final Map<File, FileObserver> observers = new HashMap<File, FileObserver>();

  private ScanTask m_scanTask;

  public FileSystemScanner(final Activity activity) {
    this.listeners =
        new EventDispatcher(
            activity, InvokationType.AsyncUI, Listener.class, ProgressListener.class);
  }

  public void shutdown() {
    stopScan();
    stopObservers();
  }

  public void startScan(final FileExtensionFilter filter, final String... paths) {
    if (inScan.compareAndSet(false, true)) {
      m_scanTask = new ScanTask(filter);
      m_scanTask.execute(paths);
    } else {
      m_scanTask.addPaths(paths);
    }
  }

  public void startScan(final FileExtensionFilter filter, final Collection<String> paths) {
    final String[] arr = paths.toArray(new String[paths.size()]);
    if (inScan.compareAndSet(false, true)) {
      m_scanTask = new ScanTask(filter);
      m_scanTask.execute(arr);
    } else {
      m_scanTask.addPaths(arr);
    }
  }

  public boolean isScan() {
    return inScan.get();
  }

  public void stopScan() {
    if (inScan.compareAndSet(true, false)) {
      m_scanTask = null;
    }
  }

  public FileObserver getObserver(final File dir) {
    // final String path = dir.getAbsolutePath();
    synchronized (observers) {
      // FileObserver fo = observers.get(path);
      FileObserver fo = observers.get(dir);
      if (fo == null) {
        fo = new FileObserverImpl(dir);
        observers.put(dir, fo);
      }
      return fo;
    }
  }

  public void removeObserver(final File dir) {
    synchronized (observers) {
      observers.remove(dir);
    }
  }

  public void stopObservers() {
    synchronized (observers) {
      for (final FileObserver o : observers.values()) {
        o.stopWatching();
      }
      observers.clear();
    }
  }

  public void addListener(final Object listener) {
    listeners.addListener(listener);
  }

  public void removeListener(final Object listener) {
    listeners.removeListener(listener);
  }

  class ScanTask extends AsyncTask<String, String, Void> {

    final FileExtensionFilter filter;

    final LinkedList<File> paths = new LinkedList<File>();

    public ScanTask(final FileExtensionFilter filter) {
      this.filter = filter;
    }

    @Override
    protected void onPreExecute() {
      final ProgressListener pl = listeners.getListener();
      pl.showProgress(true);
    }

    @Override
    protected Void doInBackground(final String... paths) {
      addPaths(paths);

      try {
        for (File dir = getDir(); dir != null && inScan.get(); dir = getDir()) {
          scanDir(dir);
        }
      } finally {
        inScan.set(false);
      }
      return null;
    }

    @Override
    protected void onPostExecute(final Void v) {
      final ProgressListener pl = listeners.getListener();
      pl.showProgress(false);
    }

    void scanDir(final File dir) {
      // Checks if scan should be continued
      if (!inScan.get()) {
        return;
      }

      if (dir == null || !dir.isDirectory()) {
        return;
      }

      if (dir.getAbsolutePath().startsWith("/sys")) {
        LCTX.d("Skip system dir: " + dir);
        return;
      }

      try {
        final File cd = CacheManager.getCacheDir();
        if (dir.getCanonicalPath().equals(cd.getCanonicalPath())) {
          LCTX.d("Skip file cache: " + dir);
          return;
        }
      } catch (final IOException ex) {
        ex.printStackTrace();
      }

      if (LCTX.isDebugEnabled()) {
        LCTX.d("Scan dir: " + dir);
      }

      // Retrieves file observer for scanning folder
      final FileObserver observer = getObserver(dir);
      // Stop watching
      observer.stopWatching();

      // Retrieves listener
      final Listener l = listeners.getListener();

      // Retrieves file list
      final File[] files = dir.listFiles((FilenameFilter) filter);
      // Sort file list
      if (LengthUtils.isNotEmpty(files)) {
        Arrays.sort(files, StringUtils.NFC);
      }
      // Call the file scan callback
      l.onFileScan(dir, files);

      // Retrieves files from current directory
      final File[] childDirs = dir.listFiles(DirectoryFilter.ALL);
      // Immediately starts folder watching
      getObserver(dir).startWatching();

      if (LengthUtils.isNotEmpty(childDirs)) {
        // Sort child dir list
        Arrays.sort(childDirs, StringUtils.NFC);
        // Add children for deep ordered scanning
        synchronized (this) {
          for (int i = childDirs.length - 1; i >= 0; i--) {
            this.paths.addFirst(childDirs[i]);
          }
        }
      }
    }

    synchronized void addPaths(final String... paths) {
      for (final String path : paths) {
        final File dir = new File(path);
        if (dir.exists() && dir.isDirectory()) {
          this.paths.add(dir);
        }
      }
    }

    synchronized File getDir() {
      return this.paths.isEmpty() ? null : this.paths.removeFirst();
    }
  }

  public class FileObserverImpl extends FileObserver {

    private final File folder;

    public FileObserverImpl(final File folder) {
      super(folder.getAbsolutePath(), EVENT_MASK);
      this.folder = folder;
    }

    @Override
    public void onEvent(final int event, final String path) {
      if (folder == null || path == null) {
        return;
      }

      final File f = new File(folder, path);
      final boolean isDirectory = f.isDirectory();
      final Listener l = listeners.getListener();

      switch (event) {
        case CLOSE_WRITE:
        case MOVED_TO:
          if (isDirectory) {
            l.onDirAdded(folder, f);
            getObserver(f).startWatching();
          } else {
            l.onFileAdded(folder, f);
          }
          break;
        case DELETE:
        case MOVED_FROM:
          if (isDirectory) {
            l.onDirDeleted(folder, f);
            removeObserver(f);
          } else {
            l.onFileDeleted(folder, f);
          }
          break;
        default:
          break;
      }
    }
  }

  public static interface Listener {

    void onFileScan(File parent, File[] files);

    void onFileAdded(File parent, File f);

    void onFileDeleted(File parent, File f);

    void onDirAdded(File parent, File f);

    void onDirDeleted(File parent, File f);
  }

  public static interface ProgressListener {

    public void showProgress(boolean show);
  }
}
public class EventGotoPage implements IEvent {

  public static final LogContext LCTX = LogManager.root().lctx("EventGotoPage");

  protected final boolean centerPage;

  protected AbstractViewController ctrl;
  protected final ViewState viewState;
  protected DocumentModel model;
  protected int viewIndex;
  protected final float offsetX;
  protected final float offsetY;

  public EventGotoPage(final AbstractViewController ctrl, final int viewIndex) {
    this.viewState = ViewState.get(ctrl);
    this.ctrl = ctrl;
    this.model = viewState.model;
    this.centerPage = true;
    this.viewIndex = viewIndex;
    this.offsetX = 0;
    this.offsetY = 0;
  }

  public EventGotoPage(
      final AbstractViewController ctrl,
      final int viewIndex,
      final float offsetX,
      final float offsetY) {
    this.viewState = ViewState.get(ctrl);
    this.ctrl = ctrl;
    this.model = viewState.model;
    this.centerPage = false;
    this.viewIndex = viewIndex;
    this.offsetX = offsetX;
    this.offsetY = offsetY;
  }

  @Override
  public ViewState process() {
    if (model == null) {
      return null;
    }

    final int pageCount = model.getPageCount();
    if (viewIndex < 0 && viewIndex >= pageCount) {
      if (LCTX.isDebugEnabled()) {
        LCTX.d("Bad page index: " + viewIndex + ", page count: " + pageCount);
      }
      return viewState;
    }

    final Page page = model.getPageObject(viewIndex);
    if (page == null) {
      if (LCTX.isDebugEnabled()) {
        LCTX.d("No page found for index: " + viewIndex);
      }
      return viewState;
    }

    model.setCurrentPageIndex(page.index);

    final IView view = ctrl.getView();

    final int scrollX = view.getScrollX();
    final int scrollY = view.getScrollY();

    final PointF p = calculateScroll(page, scrollX, scrollY);
    final int left = Math.round(p.x);
    final int top = Math.round(p.y);

    if (isScrollRequired(left, top, scrollX, scrollY)) {
      view.forceFinishScroll();
      view.scrollTo(left, top);
      viewState.update();
      return viewState;
    }
    viewState.release();

    return EventPool.newEventScrollTo(ctrl, viewIndex).process();
  }

  protected PointF calculateScroll(final Page page, final int scrollX, final int scrollY) {
    final RectF viewRect = ctrl.getView().getViewRect();
    final RectF bounds = page.getBounds(viewState.zoom);
    final float width = bounds.width();
    final float height = bounds.height();

    if (centerPage) {
      switch (ctrl.mode) {
        case HORIZONTAL_SCROLL:
          return new PointF(bounds.left - (viewRect.width() - width) / 2, scrollY);
        case VERTICALL_SCROLL:
          return new PointF(scrollX, bounds.top - (viewRect.height() - height) / 2);
      }
    }

    return new PointF(bounds.left + offsetX * width, bounds.top + offsetY * height);
  }

  protected boolean isScrollRequired(
      final int left, final int top, final int scrollX, final int scrollY) {
    switch (ctrl.mode) {
      case HORIZONTAL_SCROLL:
        return left != scrollX;
      case VERTICALL_SCROLL:
        return top != scrollY;
    }
    return true;
  }

  @Override
  public boolean process(final Page page) {
    return false;
  }

  @Override
  public boolean process(final PageTree nodes) {
    return false;
  }

  @Override
  public boolean process(final PageTree nodes, final PageTreeLevel level) {
    return false;
  }

  @Override
  public boolean process(final PageTreeNode node) {
    return false;
  }
}