Android Open Source - Android-Lib-Pen Pen Service






From Project

Back to project page Android-Lib-Pen.

License

The source code is released under:

Apache License

If you think the Android project Android-Lib-Pen listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

package android.lib.pen;
/*  w ww. j a va 2  s  .  co  m*/
import java.io.IOException;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import android.widget.ToggleButton;

import com.samsung.android.sdk.SsdkUnsupportedException;
import com.samsung.android.sdk.pen.Spen;
import com.samsung.android.sdk.pen.SpenSettingEraserInfo;
import com.samsung.android.sdk.pen.SpenSettingPenInfo;
import com.samsung.android.sdk.pen.SpenSettingViewInterface;
import com.samsung.android.sdk.pen.document.SpenInvalidPasswordException;
import com.samsung.android.sdk.pen.document.SpenNoteDoc;
import com.samsung.android.sdk.pen.document.SpenNoteFile;
import com.samsung.android.sdk.pen.document.SpenPageDoc;
import com.samsung.android.sdk.pen.document.SpenUnsupportedTypeException;
import com.samsung.android.sdk.pen.document.SpenUnsupportedVersionException;
import com.samsung.android.sdk.pen.engine.SpenColorPickerListener;
import com.samsung.android.sdk.pen.engine.SpenEraserChangeListener;
import com.samsung.android.sdk.pen.engine.SpenPenChangeListener;
import com.samsung.android.sdk.pen.engine.SpenPenDetachmentListener;
import com.samsung.android.sdk.pen.engine.SpenReplayListener;
import com.samsung.android.sdk.pen.engine.SpenSurfaceView;
import com.samsung.android.sdk.pen.engine.SpenTouchListener;
import com.samsung.android.sdk.pen.engine.SpenZoomListener;
import com.samsung.android.sdk.pen.settingui.SpenSettingEraserLayout;
import com.samsung.android.sdk.pen.settingui.SpenSettingPenLayout;

/**
 * Provides common operations for using Samsung S-Pen.
 */
public class PenService implements View.OnClickListener, SpenColorPickerListener, SpenSettingEraserLayout.EventListener, SpenPageDoc.HistoryListener, SpenReplayListener, SpenZoomListener, SpenPenChangeListener, SpenEraserChangeListener, SpenPenDetachmentListener, SpenTouchListener {
    /**
     * The pen button for selecting the pen tool and showing the {@link SpenSettingPenLayout pen setting}.
     */
    public static final int BUTTON_PEN = 0;

    /**
     * The eraser button for selecting the eraser tool and showing the {@link SpenSettingEraserLayout eraser setting}.
     */
    public static final int BUTTON_ERASER = 1;

    /**
     * The undo button used to undo in history.
     */
    public static final int BUTTON_UNDO = 2;

    /**
     * The redo button used to redo in history.
     */
    public static final int BUTTON_REDO = 3;

    /**
     * The zoom button used to zoom the canvas in 1.5x.
     */
    public static final int BUTTON_ZOOM = 4;

    /**
     * Positions a background image at the center of the {@link SpenSurfaceView canvas}.
     */
    public static final int MODE_CENTER = SpenPageDoc.BACKGROUND_IMAGE_MODE_CENTER;

    /**
     * Fits a background image at the center of the {@link SpenSurfaceView canvas} and keep its aspect ratio.
     */
    public static final int MODE_FIT = SpenPageDoc.BACKGROUND_IMAGE_MODE_FIT;

    /**
     * Tiles a background image with the {@link SpenSurfaceView canvas}.
     */
    public static final int MODE_TILE = SpenPageDoc.BACKGROUND_IMAGE_MODE_TILE;

    /**
     * Stretches a background image to fill the {@link SpenSurfaceView canvas}.
     */
    public static final int MODE_STRETCH = SpenPageDoc.BACKGROUND_IMAGE_MODE_STRETCH;

    /**
     * Replay is paused.
     * @see #getReplayState()
     */
    public static final int REPLAY_STATE_PAUSED = SpenSurfaceView.REPLAY_STATE_PAUSED;

    /**
     * Replay is playing.
     * @see #getReplayState()
     */
    public static final int REPLAY_STATE_PLAYING = SpenSurfaceView.REPLAY_STATE_PLAYING;

    /**
     * Replay is stopped.
     * @see #getReplayState()
     */
    public static final int REPLAY_STATE_STOPPED = SpenSurfaceView.REPLAY_STATE_STOPPED;

    /**
     * Replays animation at a slow speed.
     */
    public static final int SPEED_SLOW = 0;

    /**
     * Replays animation at normal speed.
     */
    public static final int SPEED_NORMAL = 1;

    /**
     * Replays animation at a fast speed.
     */
    public static final int SPEED_FAST = 2;

    private static final String NULL = new String();

    private static final Uri SPEN_SDK_MARKET_URI = Uri.parse("market://details?id=" + Spen.SPEN_NATIVE_PACKAGE_NAME); //$NON-NLS-1$

    private static final int RESPONSE_TIME = 500;

    private static final float ZOOM_RATIO = 1.5f;

    private final Activity activity;
    private final View     rootLayout;

    private SpenSurfaceView surfaceView;
    private SpenNoteDoc     noteDoc;

    private SpenSettingPenLayout    penSetting;
    private SpenSettingEraserLayout eraserSetting;

    private ToggleButton penButton;
    private ToggleButton eraserButton;
    private ToggleButton undoButton;
    private ToggleButton redoButton;
    private ToggleButton zoomButton;

    private boolean penEnabled;
    private boolean penSettingEnabled    = true;
    private boolean eraserSettingEnabled = true;
    private int     toolType             = SpenSettingViewInterface.TOOL_SPEN;
    private int     currentPage;
    private int     canvasWidth;
    private boolean dirty;
    private boolean isZoomed;

    /**
     * Determines whether a SPD file is password protected.
     * @param path the absolute path of a SPD file.
     * @return <code>true</code> if the file is password protected; otherwise, <code>false</code>.
     */
    public static boolean isLocked(final String path) {
        return SpenNoteFile.isLocked(path);
    }

    /**
     * Protects a SPD file with the specified <code>password</code>.
     * @param context an application context.
     * @param path the absolute path of a SPD file.
     * @param password the password to protect a SPD file.
     * @throws IOException thrown if the specified <code>path</code> is not found, or if a temporary directory cannot be created.
     * @throws SpenUnsupportedTypeException thrown if the specified <code>path</code> is not a SPD file.
     */
    public static void lock(final Context context, final String path, final String password) throws IOException, SpenUnsupportedTypeException {
        SpenNoteFile.lock(context, path, password);
    }

    /**
     * Unprotects a SPD file using the given <code>password</code>.
     * @param context an application context.
     * @param path the absolute path of a SPD file.
     * @param password the password to unprotect a SPD file.
     * @throws IOException  thrown if the specified <code>path</code> is not found, or if a temporary directory cannot be created.
     * @throws SpenUnsupportedTypeException thrown if the specified <code>path</code> is not a SPD file.
     * @throws SpenInvalidPasswordException thrown if the given <code>password</code> is incorrect.
     */
    public static void unlock(final Context context, final String path, final String password) throws IOException, SpenInvalidPasswordException, SpenUnsupportedTypeException {
        SpenNoteFile.unlock(context, path, password);
    }

    /**
     * Initializes tool buttons ({@link #BUTTON_PEN}, {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO})
     * and settings ({@link SpenSettingPenLayout pen setting} and {@link SpenSettingEraserLayout eraser setting}.
     * @param activity the {@link Activity} that hosts a {@link RelativeLayout}, where a {@link SpenSurfaceView canvas}
     * will be created.
     * @param rootLayout the {@link View} that contains {@link R.id.pen_container} and {@link R.id.pen_canvas}.
     */
    public PenService(final Activity activity, final View rootLayout) {
        this.activity   = activity;
        this.rootLayout = rootLayout;

        final ViewGroup penButtons = (ViewGroup)rootLayout.findViewById(R.id.pen_buttons);

        if (penButtons != null) {
            this.penButton    = (ToggleButton)penButtons.findViewById(R.id.pen_pen_button);
            this.eraserButton = (ToggleButton)penButtons.findViewById(R.id.pen_eraser_button);
            this.undoButton   = (ToggleButton)penButtons.findViewById(R.id.pen_undo_button);
            this.redoButton   = (ToggleButton)penButtons.findViewById(R.id.pen_redo_button);
            this.zoomButton   = (ToggleButton)penButtons.findViewById(R.id.pen_zoom_button);
        }
    }

    /**
     * Cleans up any resources used by the Pen package.
     */
    public void onDestroy() {
        if (this.noteDoc != null) {
            for (int i = this.noteDoc.getPageCount(); --i >= 0;) {
                final SpenPageDoc pageDoc = this.noteDoc.getPage(i);

                if (pageDoc.isRecording()) {
                    pageDoc.stopRecord();
                }
            }
        }

        if (this.penSetting != null) {
            this.penSetting.close();
        }

        if (this.eraserSetting != null) {
            this.eraserSetting.close();
        }

        if (this.surfaceView != null) {
            this.surfaceView.close();
        }

        if (this.noteDoc != null) {
            try {
                this.noteDoc.close();
            } catch (final IOException e) {
                Log.e(this.getClass().getName(), e.getMessage(), e);
            }
        }
    }

    /**
     * Called when any one of the following buttons is clicked.
     * <p>{@link R.id.pen_pen_button}, {@link R.id.pen_earser_button}, {@link R.id.pen_undo_button}, {@link R.id.pen_redo_button}</p>
     * @param view the button clicked.
     */
    @Override
    public void onClick(final View view) {
        final int id = view.getId();

        if (id == R.id.pen_pen_button) {
            this.selectButton(PenService.BUTTON_PEN);
        } else if (id == R.id.pen_eraser_button) {
            this.selectButton(PenService.BUTTON_ERASER);
        } else if (id == R.id.pen_undo_button) {
            this.undo();
        } else if (id == R.id.pen_redo_button) {
            this.redo();
        } else if (id == R.id.pen_zoom_button) {
            this.zoom();
        }
    }

    @Override
    public boolean onTouch(final View view, final MotionEvent event) {
        return false;
    }

    /**
     * Called when a color is picked.
     * @param color the color that is picked.
     * @param x the x coordinate in pixels.
     * @param y the y coordinate in pixels.
     */
    @Override
    public void onChanged(final int color, final int x, final int y) {
        if (this.penSettingEnabled) {
            if (this.surfaceView == null) {
                throw new IllegalStateException();
            }

            final SpenSettingPenInfo info = this.penSetting.getInfo();

            info.color = color;

            this.surfaceView.setPenSettingInfo(info);
            this.penSetting.setInfo(info);
        }
    }

    /**
     * Called when a {@link SpenSettingPenInfo pen setting} is changed.
     * @param info the changed {@link SpenSettingPenInfo pen setting}.
     */
    @Override
    public void onChanged(final SpenSettingPenInfo info) {
    }

    /**
     * Called when a {@link SpenSettingEraserInfo eraser setting} is changed.
     * @param info the changed {@link SpenSettingEraserInfo eraser setting}.
     */
    @Override
    public void onChanged(final SpenSettingEraserInfo info) {
    }

    /**
     * Called when the user requests to clear all objects on the {@link SpenSurfaceView canvas}.
     */
    @Override
    public void onClearAll() {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        if (this.noteDoc != null) {
            this.noteDoc.getPage(this.currentPage).removeAllObject();
        }

        this.surfaceView.update();
    }

    /**
     * Called when a new history event is committed.
     * @param the current {@link SpenPageDoc page} that commits a new history.
     */
    @Override
    public void onCommit(final SpenPageDoc doc) {
        this.dirty = true;
    }

    /**
     * Called when the undoable state is changed.
     * @param doc the current {@link SpenPageDoc page} with changed undoable state.
     * @param undoable the current undoable state.
     */
    @Override
    public void onUndoable(final SpenPageDoc doc, final boolean undoable) {
        if (this.undoButton != null) {
            this.undoButton.setEnabled(undoable);
        }
    }

    /**
     * Called when the redoable state is changed.
     * @param doc the current {@link SpenPageDoc page} with changed redoable state.
     * @param redoable the current redoable state.
     */
    @Override
    public void onRedoable(final SpenPageDoc doc, final boolean redoable) {
        if (this.redoButton != null) {
            this.redoButton.setEnabled(redoable);
        }
    }

    /**
     * Called when a replay is completed.
     */
    @Override
    public void onCompleted() {
    }

    /**
     * Called when a replay is playing.
     * @param progress the progress indicator ranging from 0 to 100.
     * @param id the object ID.
     */
    @Override
    public void onProgressChanged(final int progress, final int id) {
    }

    /**
     * Called when a canvas zoom is completed.
     * @param panX the x coordinate in pixels.
     * @param panY the y coordinate in pixels.
     * @param ratio the zoom ratio.
     */
    @Override
    public void onZoom(final float panX, final float panY, final float ratio) {
    }

    /**
     * Called when S-Pen is detached from or attached to a device.
     * @param detached <code>true</code> if S-Pen is detached; otherwise, <code>false</code>.
     */
    @Override
    public void onDetached(final boolean detached) {
    }

    /**
     * Determines if the {@link SpenNoteDoc document} is modified.
     * @return <code>true</code> if the {@link SpenNoteDoc document} is modified; otherwise, <code>false</code>.
     */
    public boolean isDirty() {
        if (this.noteDoc == null) {
            return this.dirty;
        }

        if (this.noteDoc.isChanged()) {
            return true;
        }

        return this.dirty;
    }

    /**
     * Determines whether a pen is available on the device.
     * @return <code>true</code> if a pen is available; otherwise, <code>false</code>.
     */
    public boolean isPenEnabled() {
        return this.penEnabled;
    }

    /**
     * Gets the current displayed page index.
     * @return the current displayed page index.
     */
    public int getCurrentPage() {
        return this.noteDoc == null ? -1 : this.currentPage;
    }

    /**
     * Sets the current page to display.
     * @param page the page index to display.
     */
    public void setCurrentPage(final int page) {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        if (this.noteDoc != null) {
            if (this.currentPage != page) {
                this.currentPage = page;

                this.surfaceView.setPageDoc(this.noteDoc.getPage(this.currentPage), true);
            }
        }
    }

    /**
     * Sets the background color of a page.
     * @param page the index of the page to set color to.
     * @param color the color to set.
     */
    public void setBackgroundColor(final int page, final int color) {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        if (this.noteDoc != null) {
            this.noteDoc.getPage(page).setBackgroundColor(color);
        }
    }

    /**
     * Sets the background image of a page.
     * @param page the index of the page to set color to.
     * @param imagePath the path of an image file.
     */
    public void setBackgroundImage(final int page, final String imagePath) {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        if (this.noteDoc != null) {
            this.noteDoc.getPage(page).setBackgroundImage(imagePath);
        }
    }

    /**
     * Sets the visibility of any one of the following buttons:
     * {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
     * @param button one of {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
     * @param visibility one of {@link View#VISIBLE}, {@link View#INVISIBLE} and {@link View#GONE}.
     */
    public void setButtonVisibility(final int button, final int visibility) {
        View view = null;

        switch (button) {
            case BUTTON_PEN:    view = this.penButton;    break;
            case BUTTON_ERASER: view = this.eraserButton; break;
            case BUTTON_UNDO:   view = this.undoButton;   break;
            case BUTTON_REDO:   view = this.redoButton;   break;
            case BUTTON_ZOOM:   view = this.zoomButton;   break;
        }

        if (view != null) {
            view.setVisibility(visibility);
        }
    }

    /**
     * Sets a drawable resource for a button.
     * @param button one of {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
     * @param resource a drawable resource to set.
     */
    public void setButtonResource(final int button, final int resource) {
        this.setButtonDrawable(button, this.activity.getResources().getDrawable(resource));
    }

    /**
     * Sets a drawable for a button.
     * @param button one of {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
     * @param drawable a drawable to set.
     */
    public void setButtonDrawable(final int button, final Drawable drawable) {
        ToggleButton view = null;

        switch (button) {
            case BUTTON_PEN:
                if (this.penButton != null) {
                    view = this.penButton;
                }

                break;

            case BUTTON_ERASER:
                if (this.eraserButton != null) {
                    view = this.eraserButton;
                }

                break;

            case BUTTON_UNDO:
                if (this.undoButton != null) {
                    view = this.undoButton;
                }

                break;

            case BUTTON_REDO:
                if (this.redoButton != null) {
                    view = this.redoButton;
                }

                break;

            case BUTTON_ZOOM:
                if (this.zoomButton != null) {
                    view = this.zoomButton;
                }

                break;
        }

        if (view != null) {
            view.setCompoundDrawables(null, drawable, null, null);
        }
    }

    /**
     * Determines whether {@link SpenSettingPenLayout pen setting} is enabled.
     * @return <code>true</code> if {@link SpenSettingPenLayout pen setting} is enabled; otherwise, <code>false</code>.
     */
    public boolean isPenSettingEnabled() {
        return this.penSettingEnabled;
    }

    /**
     * Enables or disables {@link SpenSettingPenLayout pen setting} when {@link #BUTTON_PEN} is clicked.
     * @param enabled <code>true</code> to enable {@link SpenSettingPenLayout pen setting}; otherwise, <code>false</code>.
     */
    public void setPenSettingEnabled(final boolean enabled) {
        this.penSettingEnabled = enabled;
    }

    /**
     * Determines whether {@link SpenSettingEraserLayout eraser layout} is enabled.
     * @return <code>true</code> if {@link SpenSettingEraserLayout eraser layout} is enabled; otherwise, <code>false</code>.
     */
    public boolean isEraserSettingEnabled() {
        return this.eraserSettingEnabled;
    }

    /**
     * Enables or disables {@link SpenSettingEraserLayout eraser setting} when {@link #BUTTON_ERASER} is clicked.
     * @param enabled <code>true</code> to enable {@link SpenSettingEraserLayout eraser setting}; otherwise, <code>false</code>.
     */
    public void setEraserSettingEnabled(final boolean enabled) {
        this.eraserSettingEnabled = enabled;
    }

    /**
     * Determines whether scroll feature is enabled.
     * @return <code>true</code> if scroll feature is enabled; otherwise, <code>false</code>.
     */
    public boolean isScrollEnabled() {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        return this.surfaceView.isHorizontalSmartScrollEnabled() || this.surfaceView.isVerticalSmartScrollEnabled();
    }

    /**
     * Enables or disables "Smart Scroll" feature.
     * <p>If enabled, the {@link SpenSurfaceView canvas} automatically scrolls when a S-Pen hovers over the edge of it.</p>
     * <p>To enable scroll feature, call this method after {@link #init(int, int, int, String, int, int, int, int)}
     * to ensure that the {@link SpenSurfaaceView canvas} layout is ready.</p>
     * @param enable <code>true</code> to enable scroll feature, or <code>false</code> to disable it. Default is <code>false</code>.
     * @param edgeSize the size of each of the 4 edges in pixels
     * @param velocity the scroll velocity in pixels.
     */
    public void setScrollEnabled(final boolean enable, final int edgeSize, final int velocity) {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        final int width  = this.surfaceView.getWidth();
        final int height = this.surfaceView.getHeight();

        this.surfaceView.setHorizontalSmartScrollEnabled(enable, new Rect(0, 0, edgeSize, height), new Rect(width, 0, width, height), PenService.RESPONSE_TIME, velocity);
        this.surfaceView.setVerticalSmartScrollEnabled(enable, new Rect(0, 0, width, 0), new Rect(0, height, width, height), PenService.RESPONSE_TIME, velocity);
    }

    /**
     * Determines whether zoom feature is enabled.
     * @return <code>true</code> if zoom feature is enabled; otherwise, <code>false</code>.
     */
    public boolean isZoomEnabled() {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        return this.surfaceView.isSmartScaleEnabled();
    }

    /**
     * Enables or disables "Smart Zoom" feature.
     * <p>If enabled, the {@link SpenSurfaceView canvas} automatically zooms when a S-Pen hovers over it.</p>
     * <p>To enable zoom feature, call this method after {@link #init(int, int, int, String, int, int, int, int)}
     * to ensure that the {@link SpenSurfaceView canvas} layout is ready.</p>
     * <p>Note: This may not work on Android 4.0 (API level 14) devices.</p>
     * @param enable <code>true</code> to enable zoom feature, or <code>false</code> to disable it. Default is <code>false</code>.
     * @param zoomRatio the zoom ratio.
     */
    public void setZoomEnabled(final boolean enable, final float zoomRatio) {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        this.surfaceView.setSmartScaleEnabled(enable, new Rect(0, 0, this.surfaceView.getWidth(), this.surfaceView.getHeight()), 8, PenService.RESPONSE_TIME, zoomRatio);
    }

    /**
     * Select a tool button to use.
     * @param button one of {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
     */
    public void selectButton(final int button) {
        if (this.surfaceView != null) {
            switch (button) {
                case BUTTON_PEN:
                    if (this.penButton != null) {
                        this.eraserSetting.setVisibility(View.GONE);

                        this.surfaceView.setToolTypeAction(this.toolType, SpenSettingViewInterface.ACTION_STROKE);

                        if (this.eraserButton != null) {
                            this.eraserButton.setChecked(false);
                        }

                        if (!this.penButton.isChecked()) {
                            this.penButton.setChecked(true);
                        }

                        this.onPenSettingClick();
                    }

                    break;

                case BUTTON_ERASER:
                    if (this.eraserButton != null) {
                        this.penSetting.setVisibility(View.GONE);

                        if (this.penButton != null) {
                            this.penButton.setChecked(false);
                        }

                        if (!this.eraserButton.isChecked()) {
                            this.eraserButton.setChecked(true);
                        }

                        this.onEraserSettingClick();
                    }

                    break;
            }
        }
    }

    /**
     * Gets the number of pages.
     * @return the number of pages.
     */
    public int getPageCount() {
        if (this.noteDoc == null) {
            return 0;
        }

        return this.noteDoc.getPageCount();
    }

    /**
     * Appends a new {@link SpenPageDoc page} with the specified background color/image.
     * @param backgroundColor the background color of the {@link SpenPageDoc page}.
     * <p>The default color will be used if <code>backgroundColor</code> is negative.</p>
     * @param backgroundImagePath the absolute path of the background image file.
     * <p>An empty background will be used if <code>backgroundImagePath</code> is empty or <code>null</code>.</p>
     * @param backgroundImageMode either {@link #MODE_CENTER}, {@link #MODE_FIT}, {@link #MODE_TILE} or {@link #MODE_STRETCH}.
     */
    public void appendPage(final int backgroundColor, final String backgroundImagePath, final int backgroundImageMode) {
        if (this.noteDoc != null) {
            final SpenPageDoc pageDoc = this.noteDoc.appendPage();
            pageDoc.setBackgroundColor(backgroundColor);

            if (!TextUtils.isEmpty(backgroundImagePath)) {
                pageDoc.setBackgroundImage(backgroundImagePath);
            }

            if (backgroundImageMode == PenService.MODE_CENTER || backgroundImageMode == PenService.MODE_FIT || backgroundImageMode == PenService.MODE_TILE || backgroundImageMode == PenService.MODE_STRETCH) {
                pageDoc.setBackgroundImageMode(backgroundImageMode);
            }

            pageDoc.clearHistory();
            pageDoc.setHistoryListener(this);
        }
    }

    /**
     * Inserts a new {@link SpenPageDoc page} at the specified page index with the specified background color/image.
     * @param backgroundColor the background color of the {@link SpenPageDoc page}.
     * <p>The default color will be used if <code>backgroundColor</code> is negative.</p>
     * @param backgroundImagePath the absolute path of the background image file.
     * <p>An empty background will be used if <code>backgroundImagePath</code> is empty or <code>null</code>.</p>
     * @param backgroundImageMode either {@link #MODE_CENTER}, {@link #MODE_FIT}, {@link #MODE_TILE} or {@link #MODE_STRETCH}.
     */
    public void insertPage(final int pageIndex, final int backgroundColor, final String backgroundImagePath, final int backgroundImageMode) {
        if (this.noteDoc != null) {
            final SpenPageDoc pageDoc = this.noteDoc.insertPage(pageIndex);
            pageDoc.setBackgroundColor(backgroundColor);

            if (!TextUtils.isEmpty(backgroundImagePath)) {
                pageDoc.setBackgroundImage(backgroundImagePath);
            }

            if (backgroundImageMode == PenService.MODE_CENTER || backgroundImageMode == PenService.MODE_FIT || backgroundImageMode == PenService.MODE_TILE || backgroundImageMode == PenService.MODE_STRETCH) {
                pageDoc.setBackgroundImageMode(backgroundImageMode);
            }

            pageDoc.clearHistory();
            pageDoc.setHistoryListener(this);
        }
    }

    /**
     * Removes a {@link SpenPageDoc page} at the specified page index.
     * @param the page index to remove.
     */
    public void removePage(final int pageIndex) {
        if (this.noteDoc != null) {
            this.noteDoc.removePage(pageIndex);
        }
    }

    /**
     * Moves a page at the specified <code>pageIndex</code> to a new index.
     * @param pageIndex the page index to move from.
     * @param step the number of page index to move.
     * <p>Moves a page forward by specifying a positive step, or backward by specifying a negative step.</p>
     */
    public void movePage(final int pageIndex, final int step) {
        if (this.noteDoc != null) {
            this.noteDoc.movePageIndex(this.noteDoc.getPage(pageIndex), step);
        }
    }

    /**
     * Undo the previous action, if any.
     */
    public void undo() {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        if (this.noteDoc != null) {
            final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage);

            if (pageDoc.isUndoable()) {
                this.surfaceView.updateUndo(pageDoc.undo());
            }
        }
    }

    /**
     * Redo the next action, if any.
     */
    public void redo() {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        if (this.noteDoc != null) {
            final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage);

            if (pageDoc.isRedoable()) {
                this.surfaceView.updateRedo(pageDoc.redo());
            }
        }
    }

    private void zoom() {
        this.isZoomed = !this.isZoomed;

        this.setZoomEnabled(this.isZoomed, PenService.ZOOM_RATIO);

        this.zoomButton.setChecked(!this.zoomButton.isChecked());
    }

    /**
     * Starts recording changes.
     * <p>By default, the {@link SpenPageDoc document) internally records any changes.</p>
     */
    public void startRecord() {
        if (this.noteDoc != null) {
            final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage);

            if (!pageDoc.isRecording()) {
                pageDoc.startRecord();
            }
        }
    }

    /**
     * Stops recording changes.
     * <p>This is a no-op if recording is not previously started.</p>
     */
    public void stopRecord() {
        if (this.noteDoc != null) {
            final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage);

            if (pageDoc.isRecording()) {
                pageDoc.stopRecord();
            }
        }
    }

    /**
     * Starts replaying the objects drawn on the {@link SpenSurfaceView canvas}, if any.
     * <p>You can replay animation by calling {@link #startRecord()}, {@link #stopRecord()},
     * {@link #startReplay()}, {@link #pauseReplay()}, {@link #resumeReplay()} and {@link #stopReplay()} methods.</p>
     * <p>It is an no-op if {@link #startRecord()} there is no object on the {@link SpenSurfaceView canvas},
     * or {@link #startRecord()} is not called before.</p>
     * @see #startRecord()
     * @see #stopRecord()
     * @see #stopReplay()
     * @see #pauseReplay()
     * @see #resumeReplay()
     */
    public void startReplay() {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        this.surfaceView.startReplay();
    }

    /**
     * Stops replaying the objects drawn on the {@link SpenSurfaceView canvas}, if any.
     * <p>You can replay animation by calling {@link #startRecord()}, {@link #stopRecord()},
     * {@link #startReplay()}, {@link #pauseReplay()}, {@link #resumeReplay()} and {@link #stopReplay()} methods.</p>
     * <p>It is an no-op if {@link #startReplay()} is not called before.</p>
     * @see #startRecord()
     * @see #stopRecord()
     * @see #startReplay()
     * @see #pauseReplay()
     * @see #resumeReplay()
     */
    public void stopReplay() {
        if (this.surfaceView == null) {
            //throw new IllegalStateException();
        } else {
            if (this.surfaceView.getReplayState() == SpenSurfaceView.REPLAY_STATE_PLAYING) {
                this.surfaceView.stopReplay();
            }
        }
    }

    /**
     * Pauses replaying the objects drawn on the {@link SpenSurfaceView canvas}, if any.
     * <p>You can replay animation by calling {@link #startRecord()}, {@link #stopRecord()},
     * {@link #startReplay()}, {@link #pauseReplay()}, {@link #resumeReplay()} and {@link #stopReplay()} methods.</p>
     * <p>It is an no-op if {@link #startReplay()} is not called before.</p>
     * @see #startRecord()
     * @see #stopRecord()
     * @see #startReplay()
     * @see #stopReplay()
     * @see #resumeReplay()
     */
    public void pauseReplay() {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        this.surfaceView.pauseReplay();
    }

    /**
     * Resumes replaying the objects drawn on the {@link SpenSurfaceView canvas}, if any.
     * <p>You can replay animation by calling {@link #startRecord()}, {@link #stopRecord()},
     * {@link #startReplay()}, {@link #pauseReplay()}, {@link #resumeReplay()} and {@link #stopReplay()} methods.</p>
     * <p>It is an no-op if {@link #pauseReplay()} is not called before.</p>
     * @see #startRecord()
     * @see #stopRecord()
     * @see #startReplay()
     * @see #stopReplay()
     * @see #pauseReplay()
     */
    public void resumeReplay() {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        this.surfaceView.resumeReplay();
    }

    /**
     * Gets the animation replay state.
     * @return the current replay state.
     * <p>Could be one of these values: {@link #REPLAY_STATE_PAUSED}, {@link #REPLAY_STATE_PLAYING} or {@link #REPLAY_STATE_STOPPED}.</p>
     */
    public int getReplayState() {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        return this.surfaceView.getReplayState();
    }

    /**
     * Sets the speed for replaying objects drawn on the {@link SpenSurfaceView canvas}.
     * @param speed the replay speed.
     * <p>The valid values are {@link #SPEED_SLOW}, {@link #SPEED_NORMAL} and {@link #SPEED_FAST}.</p>
     */
    public void setReplaySpeed(final int speed) {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        this.surfaceView.setReplaySpeed(speed);
    }

    /**
     * Creates a {@link Bitmap} from the current {@link SpenPageDoc page}.
     * @param scale the scale to resize the generated {@link Bitmap}.
     * @return the {@link Bitmap} captured from the current {@link SpenPageDoc page}.
     */
    public Bitmap generateThumbnail(final float scale) {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        return this.surfaceView.capturePage(scale);
    }

    /**
     * Saves the {@link SpenNoteDoc document} to a SPD file at the specified <code>path</code>.
     * @param path the absolute path to save a SPD file to.
     * @throws IOException if the operation fails.
     */
    public void save(final String path) throws IOException {
        if (this.dirty && this.noteDoc != null) {
            this.noteDoc.save(path);
        }
    }

    /**
     * Loads a SPD file into the current {@link SpenNoteDoc document}.
     * @param path the absolute path of a SPD file to load.
     * @param writable <code>true</code> if the SPD file should be writable; otherwise, <code>false</code>.
     * @throws IOException thrown if the specified <code>path</code> is not found or a cache directory cannot be generated.
     * @throws SpenUnsupportedTypeException thrown if the file to load is not in SPD format.
     * @throws SpenUnsupportedVersionException thrown if the Pen package installed on the device is incompatible.
     * @throws SpenInvalidPasswordException thrown if the file is password protected.
     */
    public void load(final String path, final boolean writable) throws SpenInvalidPasswordException, SpenUnsupportedTypeException, SpenUnsupportedVersionException, IOException {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        final SpenNoteDoc noteDoc = new SpenNoteDoc(this.activity, path, this.canvasWidth, writable ? SpenNoteDoc.MODE_WRITABLE : SpenNoteDoc.MODE_READ_ONLY);

        if (this.noteDoc != null) {
            this.noteDoc.close();
        }

        this.noteDoc = noteDoc;

        if (this.noteDoc.getPageCount() > 0) {
            this.currentPage = 0;

            final SpenPageDoc page = this.noteDoc.getPage(0);
            page.setHistoryListener(this);

            this.surfaceView.setPageDoc(page, true);
            this.surfaceView.update();
        }

        if (this.noteDoc.getPageCount() > 1) {
            this.noteDoc.getPage(1).setHistoryListener(this);
        }
    }

    /**
     * Loads a SPD file into the current {@link SpenNoteDoc document}.
     * @param path the absolute path of a SPD file to load.
     * @param writable <code>true</code> if the SPD file should be writable; otherwise, <code>false</code>.
     * @throws IOException thrown if the specified <code>path</code> is not found or a cache directory cannot be generated.
     * @throws SpenUnsupportedTypeException thrown if the file to load is not in SPD format.
     * @throws SpenUnsupportedVersionException thrown if the Pen package installed on the device is incompatible.
     * @throws SpenInvalidPasswordException thrown if the specified <code>password</code> is incorrect.
     */
    public void load(final String path, final String password, final boolean writable) throws SpenInvalidPasswordException, SpenUnsupportedTypeException, SpenUnsupportedVersionException, IOException {
        if (this.surfaceView == null) {
            throw new IllegalStateException();
        }

        final SpenNoteDoc noteDoc = new SpenNoteDoc(this.activity, path, password, this.canvasWidth, writable ? SpenNoteDoc.MODE_WRITABLE : SpenNoteDoc.MODE_READ_ONLY, !writable);

        if (this.noteDoc != null) {
            this.noteDoc.close();
        }

        this.noteDoc = noteDoc;

        if (this.noteDoc.getPageCount() > 0) {
            this.currentPage = 0;

            final SpenPageDoc page = this.noteDoc.getPage(0);
            page.setHistoryListener(this);

            this.surfaceView.setPageDoc(page, true);
            this.surfaceView.update();
        }

        if (this.noteDoc.getPageCount() > 1) {
            this.noteDoc.getPage(1).setHistoryListener(this);
        }
    }

    /**
     * Initialize S-Pen related objects.
     * <p>This will create a {@link SpenSurfaceView canvas} and a {@link SpenNoteDoc document} objects,
     * and append a {@link SpenPageDoc page} to it.</p>
     * <p>If S-Pen is not supported on the device, a message will be displayed and {@link Activity#finish()} will be called.</p>
     * @param canvasWidth the width of the {@link SpenSurfaceView canvas} to put a {@link SpenPageDoc page} in it.
     * @param canvasHeight the height of the {@link SpenSurfaceView canvas} to put a {@link SpenPageDoc page} in it.
     * @param backgroundColor the background color of the {@link SpenPageDoc page}.
     * <p>The default color will be used if <code>backgroundColor</code> is negative.</p>
     * @param backgroundImagePath the absolute path of the background image file.
     * <p>An empty background will be used if <code>backgroundImagePath</code> is empty or <code>null</code>.</p>
     * @param backgroundImageMode either {@link #MODE_CENTER}, {@link #MODE_FIT}, {@link #MODE_TILE} or {@link #MODE_STRETCH}.
     * @param penColor the initial pen color to use.
     * @param penSize the initial pen size in pixels to use.
     * @param eraserSize the initial eraser size in pixels to use.
     * @return <code>true</code> if Spen SDK is initialized successfully; otherwise, <code>false</code>.
     */
    public boolean init(final int canvasWidth, final int canvasHeight, final int backgroundColor, final String backgroundImagePath, final int backgroundImageMode, final int penColor, final int penSize, final int eraserSize) {
        if (this.initSpen()) {
            final RelativeLayout canvas = (RelativeLayout)this.rootLayout.findViewById(R.id.pen_canvas);

            if (this.penSettingEnabled) {
                this.penSetting = new SpenSettingPenLayout(this.activity, PenService.NULL, canvas);
            }

            if (this.eraserSettingEnabled) {
                this.eraserSetting = new SpenSettingEraserLayout(this.activity, PenService.NULL, canvas);
            }

            this.initCanvas(canvasWidth, canvasHeight, backgroundColor, backgroundImagePath, backgroundImageMode);
            this.initPenInfo(penColor, penSize);
            this.initEraserInfo(eraserSize);

            if (this.surfaceView != null) {
                this.surfaceView.setColorPickerListener(this);
            }

            if (this.eraserSetting != null) {
                this.eraserSetting.setEraserListener(this);
            }

            if (this.penButton != null) {
                this.penButton.setOnClickListener(this);
            }

            if (this.eraserButton != null) {
                this.eraserButton.setOnClickListener(this);
            }

            if (this.undoButton != null) {
                this.undoButton.setOnClickListener(this);
            }

            if (this.redoButton != null) {
                this.redoButton.setOnClickListener(this);
            }

            if (this.zoomButton != null) {
                this.zoomButton.setOnClickListener(this);
            }

            final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage);

            if (this.undoButton != null) {
                this.undoButton.setEnabled(pageDoc.isUndoable());
            }

            if (this.redoButton != null) {
                this.redoButton.setEnabled(pageDoc.isRedoable());
            }

            pageDoc.setHistoryListener(this);

            this.selectButton(PenService.BUTTON_PEN);
            this.onPenSettingClick();

            return true;
        }

        return false;
    }

    private boolean initSpen() {
        final Spen spen = new Spen();

        try {
            spen.initialize(this.activity);

            this.penEnabled = spen.isFeatureEnabled(Spen.DEVICE_PEN);

            return true;
        } catch (final SsdkUnsupportedException e) {
            Log.i(this.getClass().getName(), e.getMessage(), e);

            this.handleUnsupportedException(e);
        } catch (final Exception e) {
            Toast.makeText(this.activity, R.string.message_spen_not_initialized, Toast.LENGTH_SHORT).show();

            this.activity.finish();
        }

        return false;
    }

    private void initCanvas(final int canvasWidth, final int canvasHeight, final int backgroundColor, final String backgroundImagePath, final int backgroundImageMode) {
        final ViewGroup canvas    = (ViewGroup)this.rootLayout.findViewById(R.id.pen_canvas);
        final ViewGroup container = (ViewGroup)this.rootLayout.findViewById(R.id.pen_container);

        this.canvasWidth = canvasWidth;
        this.surfaceView = new SpenSurfaceView(this.activity);

        canvas.addView(this.surfaceView);

        try {
            this.noteDoc = new SpenNoteDoc(this.activity, canvasWidth, canvasHeight);
        } catch (final IOException e) {
            Toast.makeText(this.activity, R.string.message_spen_not_initialized, Toast.LENGTH_SHORT).show();

            this.activity.finish();
        } catch (final Exception e) {
            Toast.makeText(this.activity, R.string.message_spen_not_initialized, Toast.LENGTH_SHORT).show();

            this.activity.finish();
        }

        if (this.noteDoc != null) {
            final SpenPageDoc pageDoc = this.noteDoc.appendPage();
            pageDoc.setBackgroundColor(backgroundColor);

            if (!TextUtils.isEmpty(backgroundImagePath)) {
                pageDoc.setBackgroundImage(backgroundImagePath);
            }

            if (backgroundImageMode == PenService.MODE_CENTER || backgroundImageMode == PenService.MODE_FIT || backgroundImageMode == PenService.MODE_TILE || backgroundImageMode == PenService.MODE_STRETCH) {
                pageDoc.setBackgroundImageMode(backgroundImageMode);
            }

            pageDoc.clearHistory();
            pageDoc.setHistoryListener(this);

            this.surfaceView.setPageDoc(pageDoc, true);

            if (this.penEnabled) {
                this.surfaceView.setToolTypeAction(SpenSettingViewInterface.TOOL_FINGER, SpenSettingViewInterface.ACTION_NONE);

                this.toolType = SpenSettingViewInterface.TOOL_SPEN;
            } else {
                this.surfaceView.setToolTypeAction(SpenSettingViewInterface.TOOL_FINGER, SpenSettingViewInterface.ACTION_STROKE);

                this.toolType = SpenSettingViewInterface.TOOL_FINGER;

                Toast.makeText(this.activity, R.string.message_finger_only, Toast.LENGTH_SHORT).show();
            }
        }

        if (this.penSettingEnabled) {
            container.addView(this.penSetting);
            this.penSetting.setCanvasView(this.surfaceView);
        }

        if (this.eraserSettingEnabled) {
            container.addView(this.eraserSetting);
            this.eraserSetting.setCanvasView(this.surfaceView);
        }

        this.surfaceView.setReplayListener(this);
        this.surfaceView.setZoomListener(this);
        this.surfaceView.setPenChangeListener(this);
        this.surfaceView.setEraserChangeListener(this);
        this.surfaceView.setPenDetachmentListener(this);
        this.surfaceView.setTouchListener(this);
    }

    private void initPenInfo(final int penColor, final int penSize) {
        if (this.penSettingEnabled) {
            if (this.surfaceView == null) {
                throw new IllegalStateException();
            }

            final SpenSettingPenInfo info = new SpenSettingPenInfo();

            info.color = penColor;
            info.size  = penSize;

            this.surfaceView.setPenSettingInfo(info);
            this.penSetting.setInfo(info);
        }
    }

    private void initEraserInfo(final int eraserSize) {
        if (this.eraserSettingEnabled) {
            if (this.surfaceView == null) {
                throw new IllegalStateException();
            }

            final SpenSettingEraserInfo info = new SpenSettingEraserInfo();

            info.size = eraserSize;

            this.surfaceView.setEraserSettingInfo(info);
            this.eraserSetting.setInfo(info);
        }
    }

    private void onPenSettingClick() {
        if (this.penSettingEnabled) {
            if (this.penSetting.isShown()) {
                this.penSetting.setVisibility(View.GONE);
            } else {
                this.penSetting.setViewMode(SpenSettingPenLayout.VIEW_MODE_EXTENSION);

                final View                     toolsLayout = (View)this.rootLayout.findViewById(R.id.pen_buttons).getParent();
                final FrameLayout.LayoutParams params      = (FrameLayout.LayoutParams)this.penSetting.getLayoutParams();
                params.leftMargin = (int)toolsLayout.getX() + this.penButton.getLeft();
                params.topMargin  = (int)toolsLayout.getY() + this.penButton.getTop() + this.penButton.getHeight();

                this.penSetting.setLayoutParams(params);
                this.penSetting.setVisibility(View.VISIBLE);
            }
        }
    }

    private void onEraserSettingClick() {
        if (this.eraserSettingEnabled) {
            if (this.surfaceView.getToolTypeAction(this.toolType) == SpenSettingViewInterface.ACTION_ERASER) {
                if (this.eraserSetting.isShown()) {
                    this.eraserSetting.setVisibility(View.GONE);
                } else {
                    this.eraserSetting.setViewMode(SpenSettingEraserLayout.VIEW_MODE_NORMAL);

                    final View                     toolsLayout = (View)this.rootLayout.findViewById(R.id.pen_buttons).getParent();
                    final FrameLayout.LayoutParams params      = (FrameLayout.LayoutParams)this.eraserSetting.getLayoutParams();
                    params.leftMargin = (int)toolsLayout.getX() + this.eraserButton.getLeft();
                    params.topMargin  = (int)toolsLayout.getY() + this.eraserButton.getTop() + this.eraserButton.getHeight();

                    this.eraserSetting.setLayoutParams(params);
                    this.eraserSetting.setVisibility(View.VISIBLE);
                }
            } else {
                this.surfaceView.setToolTypeAction(this.toolType, SpenSettingViewInterface.ACTION_ERASER);
            }
        }
    }

    private void handleUnsupportedException(final SsdkUnsupportedException e) {
        int     messageId = R.string.message_spen_not_initialized;
        boolean isFatal   = false;

        switch (e.getType()) {
            case SsdkUnsupportedException.VENDOR_NOT_SUPPORTED:
                messageId = R.string.message_non_samsung_device;
                isFatal   = true;

                break;

            case SsdkUnsupportedException.DEVICE_NOT_SUPPORTED:
                messageId = R.string.message_spen_not_found;
                isFatal   = true;

                break;

            case SsdkUnsupportedException.LIBRARY_NOT_INSTALLED:
                messageId = R.string.message_spen_library_not_found;

                break;

            case SsdkUnsupportedException.LIBRARY_UPDATE_IS_REQUIRED:
            case SsdkUnsupportedException.LIBRARY_UPDATE_IS_RECOMMENDED:
                messageId = R.string.message_spen_library_update_required;

                break;
        }

        if (isFatal) {
            Toast.makeText(this.activity, messageId, Toast.LENGTH_SHORT).show();

            this.activity.finish();
        } else {
            new AlertDialog.Builder(this.activity).setIcon(android.R.drawable.ic_dialog_alert).setTitle(R.string.message_update).setMessage(messageId).setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                @SuppressWarnings("synthetic-access")
                @Override
                public void onClick(final DialogInterface dialog, final int which) {
                    PenService.this.activity.startActivity(new Intent(Intent.ACTION_VIEW, PenService.SPEN_SDK_MARKET_URI).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));

                    dialog.dismiss();

                    PenService.this.activity.finish();
                }
            }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                @SuppressWarnings({"synthetic-access"})
                @Override
                public void onClick(final DialogInterface dialog, final int which) {
                    dialog.dismiss();

                    PenService.this.activity.finish();
                }
            }).show();
        }
    }
}




Java Source Code List

android.lib.pen.PenButton.java
android.lib.pen.PenService.java
android.lib.pen.demo.Constants.java
android.lib.pen.demo.DragDropUtils.java
android.lib.pen.demo.DrawingActivity.java
android.lib.pen.demo.DrawingService.java
android.lib.pen.demo.GalleryAdapter.java
android.lib.pen.demo.IOUtils.java
android.lib.pen.demo.MainActivity.java
android.lib.pen.demo.MultiDragListener.java
android.lib.pen.demo.OnPageUpdatedListener.java
android.lib.pen.demo.OnReplayCompletedListener.java