Back to project page Android-Lib-Pen.
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.
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(); } } }