6 |
7 | Supported features
8 | --------
9 | * Pen of various styles, colors and adjustable size
10 | * Eraser of adjustable size
11 | * Undo, redo
12 | * Zoom
13 | * Scroll automatically when the pen is hovering near an edge of the canvas
14 | * Replay of strokes with different speed
15 | * Instance thumbnail generation
16 | * Multi-page support
17 | * Re-order pages
18 | * Color background
19 | * Image background
20 | * Supported events: `onTouch`, `onReplayCompleted`, `onPageUpdated`, `onCommit`, `undo`, `redo`
21 | * Draw using S-Pen or finger
22 |
23 | Getting started
24 | ---------------
25 | Your target users will need to install the S-Pen SDK package before they can use any Pen features. In the official sample code, the user will be redirected to Google Play Store to download the SDK package developed by Samsung. This library handles this for you and prompt the user for any necessary action.
26 |
27 | **Note**: If you plan to submit your app to Samsung Seller Office, they may sometimes reject your submission because your app links to external store (i.e. download Samsung Pen SDK from Google Play Store). You will need to explain to them that this is required as documented in their official sample code.
28 |
29 | Using the wrapper
30 | -----------------
31 |
32 | **Initialization**
33 |
34 | `PenService.init(canvasWidth, canvasHeight, backgroundColor, backgroundImagePath, fittingMode, initialPenColor, initialPenSize, initialEraserSize);`
35 |
36 | * You need to create a drawing canvas of a specific dimensions. The dimension cannot be changed later.
37 | * You can either specify a background color or background image but not both. The background color is ignored if a background image is specified.
38 | * `fittingMode` specifies how to fit the background image (best-fit, scaled, cropped, etc.)
39 | * You need to specify the initial pen color and size, and eraser size.
40 |
41 | **Create a new drawing**
42 |
43 | `PenService.appendPage(backgroundColor, backgroundImagePath, fittingMode);`
44 |
45 | * You need to append a page before you can draw on the canvas.
46 |
47 | **Load an existing drawing**
48 |
49 | `PenService.load(spdFilePath, writable);`
50 |
51 | * This will load a .spd file to the canvas
52 | * If you want the drawing to be read-only, set `writable` to `false`. This is useful if you want to replay the strokes only.
53 | * Setting `writable` to `true` will initialize the history stack, which consumes more RAM so use this wisely.
54 |
55 | **Drawing**
56 |
57 | After you created a new drawing or loaded an existing drawing, you are ready to drawing on it. Users can draw using S-Pen, if any. For devices without S-Pen, they can draw using their fingers.
58 |
59 | **Tools**
60 |
61 | This library provides various ready-to-use tools, including pen, eraser, undo, redo, zoom, and replay. You can use these tools by using the provided UI, or by invoking the APIs directly.
62 |
63 | Demo
64 | ----
65 | A demo app is provided so you know what you can expect from this library.
66 |
67 | This demo shows you how to:
68 |
69 | * Create a new drawing
70 | * Load an existing drawing
71 | * Update thumbnail instantly
72 | * Undo, redo
73 | * Save any changes
74 |
75 | Supported devices
76 | -----------------
77 | Samsung does not specify which devices are supported. Currently this library has been tested on:
78 |
79 | * Galaxy S5
80 | * Galaxy S4
81 | * Galaxy S3
82 | * Galaxy S2
83 | * Galaxy Note 3
84 | * Galaxy Note 3 Neo
85 | * Galaxy Note 2
86 | * Galaxy Note 10.1 (2014 Edition)
87 | * Galaxy NotePro 12.2
88 | * Galaxy Camera
89 | * Galaxy NX
90 | * Galaxy Mega 6.3
91 | * Galaxy Nexus
92 | * Galaxy Premier
93 | * Galaxy J
94 | * Galaxy Grand 2
95 | * Galaxy Round
96 | * Galaxy TabPro 8.4
97 | * Galaxy TabPro 10.1
98 | * Galaxy TabPro 12.2
99 |
100 | Limitation
101 | ----------
102 |
103 | * Minimum canvas size is 480 x 800
104 | * The canvas size you initialize `PenService` with cannot be changed later
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
203 |
--------------------------------------------------------------------------------
/demo/src/android/lib/pen/demo/DrawingActivity.java:
--------------------------------------------------------------------------------
1 | package android.lib.pen.demo;
2 |
3 | import java.io.File;
4 | import java.io.IOException;
5 | import java.util.UUID;
6 |
7 | import android.app.Activity;
8 | import android.app.AlertDialog;
9 | import android.app.ProgressDialog;
10 | import android.content.DialogInterface;
11 | import android.lib.pen.PenService;
12 | import android.os.AsyncTask;
13 | import android.os.Bundle;
14 | import android.os.Handler;
15 | import android.os.Message;
16 | import android.util.DisplayMetrics;
17 | import android.util.Log;
18 | import android.view.View;
19 | import android.widget.ImageView;
20 | import android.widget.Toast;
21 |
22 | import com.samsung.android.sdk.pen.document.SpenInvalidPasswordException;
23 | import com.samsung.android.sdk.pen.document.SpenUnsupportedTypeException;
24 | import com.samsung.android.sdk.pen.document.SpenUnsupportedVersionException;
25 |
26 | public final class DrawingActivity extends Activity implements Runnable, OnPageUpdatedListener, OnReplayCompletedListener {
27 | private DrawingService service;
28 |
29 | private ImageView imageView;
30 |
31 | private String spdPath;
32 |
33 | @Override
34 | protected void onCreate(final Bundle savedInstanceState) {
35 | super.onCreate(savedInstanceState);
36 |
37 | this.setResult(Activity.RESULT_CANCELED);
38 |
39 | this.setContentView(R.layout.activity_drawing);
40 |
41 | // Gets the .spd file path
42 | this.spdPath = this.getIntent().getStringExtra(Constants.EXTRA_SPD_PATH);
43 |
44 | if (this.spdPath != null && !new File(this.spdPath).exists()) {
45 | Toast.makeText(this, R.string.message_spd_not_found, Toast.LENGTH_SHORT).show();
46 |
47 | this.finish();
48 | }
49 |
50 | // Creates a PenService. It will use the root layout specified here to get the @id/pen_container and @id/pen_canvas
51 | this.service = new DrawingService(this, this.findViewById(R.id.root_layout));
52 |
53 | // We will update the canvas thumbnail when it changes
54 | this.imageView = (ImageView)this.findViewById(R.id.preview);
55 |
56 | // Adds drag & drop support for the tools layout and the thumbnail, so that the user can draw anywhere on the canvas by moving them around
57 | DragDropUtils.addDragDropSupport(this.findViewById(R.id.drag_tools), this.findViewById(R.id.tools_layout), this.findViewById(R.id.root_layout));
58 | DragDropUtils.addDragDropSupport(this.findViewById(R.id.drag_preview), this.findViewById(R.id.preview_layout), this.findViewById(R.id.root_layout));
59 |
60 | // It is better to initialize PenService asynchronously because it takes time
61 | new Handler().post(this);
62 | }
63 |
64 | @Override
65 | protected void onDestroy() {
66 | // Remember to call PenService.onDestroy() in Activity.onDestroy()
67 | if (this.service != null) {
68 | this.service.onDestroy();
69 | }
70 |
71 | super.onDestroy();
72 | }
73 |
74 | @Override
75 | public void onBackPressed() {
76 | // Asks the user to save the drawing if it is modified
77 | if (this.service != null && this.service.isDirty()) {
78 | new AlertDialog.Builder(this)
79 | .setIcon(android.R.drawable.ic_dialog_info)
80 | .setTitle(R.string.dialog_unsaved)
81 | .setMessage(R.string.dialog_unsaved_message)
82 | .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
83 | @Override
84 | public void onClick(final DialogInterface dialog, final int which) {
85 | DrawingActivity.this.save();
86 | }
87 | })
88 | .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
89 | @Override
90 | public void onClick(final DialogInterface dialog, final int which) {
91 | DrawingActivity.super.onBackPressed();
92 | }
93 | })
94 | .show();
95 | } else {
96 | super.onBackPressed();
97 | }
98 | }
99 |
100 | @Override
101 | public void onPageUpdated() {
102 | // Updates thumbnail whenever the page is changed
103 | this.updateThumbnail();
104 | }
105 |
106 | @Override
107 | public void onReplayCompleted() {
108 | if (this.service != null) {
109 | // Makes sure the replay is stopped
110 | this.service.stopReplay();
111 |
112 | // (Optional) Records strokes for replay later. Mostly for eye-candy.
113 | this.service.startRecord();
114 | }
115 | }
116 |
117 | @Override
118 | public void run() {
119 | if (this.spdPath == null) {
120 | // Creates a new drawing and prepares it for editing
121 | if (this.prepareEditNewDrawing()) {
122 | // (Optional) Records strokes for replay later. Mostly for eye-candy.
123 | this.service.startRecord();
124 | }
125 | } else {
126 | // Loads an existing drawing and prepares it for editing
127 | if (this.prepareEditOldDrawing()) {
128 | // (Optional) Replay strokes. Mostly for eye-candy.
129 | this.service.startReplay();
130 | }
131 | }
132 | }
133 |
134 | /**
135 | * Initializes PenService for a blank drawing.
136 | * @return true if PenService is initialized successfully; otherwise, false.
137 | */
138 | private boolean prepareEditNewDrawing() {
139 | if (this.initCanvas()) {
140 | // Creates a new page for the drawing.
141 | // Here we use a white background. You may use an image background instead.
142 | // If an image background is used, the color background will be ignored because transparent image background is not supported.
143 | this.service.appendPage(Constants.CANVAS_BACKGROUND_COLOR, null, PenService.MODE_FIT);
144 |
145 | // Updates the thumbnail after the page is changed.
146 | this.updateThumbnail();
147 |
148 | return true;
149 | }
150 |
151 | return false;
152 | }
153 |
154 | /**
155 | * Initializes PenService for an existing drawing.
156 | * @return true if PenService is initialized successfully; otherwise, false.
157 | */
158 | private boolean prepareEditOldDrawing() {
159 | if (this.initCanvas()) {
160 | try {
161 | // Loads an existing .spd file and make it editable.
162 | this.service.load(this.spdPath, true);
163 |
164 | // Updates the thumbnail after the page is changed.
165 | this.updateThumbnail();
166 |
167 | return true;
168 | } catch (final SpenInvalidPasswordException e) { // If the .spd file is password-protected, we must use this.service.load(this.spdPath, password, true) instead.
169 | Toast.makeText(this, R.string.message_spd_locked, Toast.LENGTH_SHORT).show();
170 |
171 | this.finish();
172 | } catch (final SpenUnsupportedTypeException e) { // The .spd file is not supported for whatever reason
173 | Toast.makeText(this, R.string.message_spd_unsupported, Toast.LENGTH_SHORT).show();
174 |
175 | this.finish();
176 | } catch (final SpenUnsupportedVersionException e) {
177 | Toast.makeText(this, R.string.message_spen_library_update_required, Toast.LENGTH_SHORT).show();
178 |
179 | this.finish();
180 | } catch (final IOException e) {
181 | Toast.makeText(this, R.string.message_spd_not_found, Toast.LENGTH_SHORT).show();
182 |
183 | this.finish();
184 | }
185 | }
186 |
187 | return false;
188 | }
189 |
190 | /**
191 | * Initializes the canvas and sets its dimensions the same as the screen size.
192 | * @return true if the PenService is initialized successfully; otherwise, false.
193 | */
194 | private boolean initCanvas() {
195 | final DisplayMetrics metrics = new DisplayMetrics();
196 | this.getWindowManager().getDefaultDisplay().getMetrics(metrics);
197 |
198 | if (this.service.init(metrics.widthPixels, metrics.heightPixels, Constants.CANVAS_BACKGROUND_COLOR, null, PenService.MODE_FIT, Constants.DEFAULT_PEN_COLOR, Constants.DEFAULT_PEN_SIZE, Constants.DEFAULT_ERASER_SIZE)) {
199 | // (Optional) If the canvas is larger than the screen, we can enable "smart scrolling". It will automatically scroll with the specified speed when the pen is near the specified edge pixels.
200 | this.service.setScrollEnabled(false, (int)(Math.max(metrics.widthPixels, metrics.heightPixels) * Constants.CANVAS_SCROLL_EDGE), PenService.SPEED_NORMAL);
201 |
202 | // (Optional) Enables zooming for double-tap (by finger or pen)
203 | this.service.setZoomEnabled(true, Constants.CANVAS_ZOOM_SCALE);
204 |
205 | this.service.setButtonVisibility(PenService.BUTTON_ZOOM, View.GONE);
206 |
207 | // Attaches OnPageUpdatedListener after everything is initialized
208 | this.service.setOnPageUpdatedListener(this);
209 |
210 | this.updateThumbnail();
211 |
212 | return true;
213 | }
214 |
215 | return false;
216 | }
217 |
218 | /**
219 | * Updates the thumbnail.
220 | */
221 | private void updateThumbnail() {
222 | // Generates a Bitmap by scaling the canvas by a specific ratio
223 | this.imageView.setImageBitmap(this.service.generateThumbnail(Constants.CANVAS_PREVIEW_SCALE));
224 | }
225 |
226 | /**
227 | * Saves the drawing to a .spd file.
228 | * It is better to save asynchronously because it takes time.
229 | */ 230 | private void save() { 231 | final ProgressDialog progressDialog = ProgressDialog.show(this, this.getString(R.string.save), this.getString(R.string.wait), true, false); 232 | 233 | final Handler handler = new Handler() { 234 | @Override 235 | public void handleMessage(final Message message) { 236 | progressDialog.dismiss(); 237 | 238 | if (((Boolean)message.obj).booleanValue()) { 239 | Toast.makeText(DrawingActivity.this, R.string.message_saved, Toast.LENGTH_SHORT).show(); 240 | 241 | // Tells MainActivity that we have created a new drawing so it refreshes the dashboard 242 | DrawingActivity.this.setResult(Activity.RESULT_OK, DrawingActivity.this.getIntent().putExtra(Constants.EXTRA_SPD_PATH, DrawingActivity.this.spdPath)); 243 | 244 | DrawingActivity.this.finish(); 245 | } else { 246 | Toast.makeText(DrawingActivity.this, R.string.message_save_error, Toast.LENGTH_SHORT).show(); 247 | } 248 | } 249 | }; 250 | 251 | new AsyncTasktrue if the thumbnail is saved successfully; otherwise, false.
296 | */
297 | private boolean saveThumbnail(final String thumbnailPath) {
298 | // Generates a thumbnail by the specified scale and saves it
299 | return IOUtils.write(thumbnailPath, this.service.generateThumbnail(Constants.THUMBNAIL_SCALE));
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/src/android/lib/pen/PenService.java:
--------------------------------------------------------------------------------
1 | package android.lib.pen;
2 |
3 | import java.io.IOException;
4 |
5 | import android.app.Activity;
6 | import android.app.AlertDialog;
7 | import android.content.Context;
8 | import android.content.DialogInterface;
9 | import android.content.Intent;
10 | import android.graphics.Bitmap;
11 | import android.graphics.Rect;
12 | import android.graphics.drawable.Drawable;
13 | import android.net.Uri;
14 | import android.text.TextUtils;
15 | import android.util.Log;
16 | import android.view.MotionEvent;
17 | import android.view.View;
18 | import android.view.ViewGroup;
19 | import android.widget.FrameLayout;
20 | import android.widget.RelativeLayout;
21 | import android.widget.Toast;
22 | import android.widget.ToggleButton;
23 |
24 | import com.samsung.android.sdk.SsdkUnsupportedException;
25 | import com.samsung.android.sdk.pen.Spen;
26 | import com.samsung.android.sdk.pen.SpenSettingEraserInfo;
27 | import com.samsung.android.sdk.pen.SpenSettingPenInfo;
28 | import com.samsung.android.sdk.pen.SpenSettingViewInterface;
29 | import com.samsung.android.sdk.pen.document.SpenInvalidPasswordException;
30 | import com.samsung.android.sdk.pen.document.SpenNoteDoc;
31 | import com.samsung.android.sdk.pen.document.SpenNoteFile;
32 | import com.samsung.android.sdk.pen.document.SpenPageDoc;
33 | import com.samsung.android.sdk.pen.document.SpenUnsupportedTypeException;
34 | import com.samsung.android.sdk.pen.document.SpenUnsupportedVersionException;
35 | import com.samsung.android.sdk.pen.engine.SpenColorPickerListener;
36 | import com.samsung.android.sdk.pen.engine.SpenEraserChangeListener;
37 | import com.samsung.android.sdk.pen.engine.SpenPenChangeListener;
38 | import com.samsung.android.sdk.pen.engine.SpenPenDetachmentListener;
39 | import com.samsung.android.sdk.pen.engine.SpenReplayListener;
40 | import com.samsung.android.sdk.pen.engine.SpenSurfaceView;
41 | import com.samsung.android.sdk.pen.engine.SpenTouchListener;
42 | import com.samsung.android.sdk.pen.engine.SpenZoomListener;
43 | import com.samsung.android.sdk.pen.settingui.SpenSettingEraserLayout;
44 | import com.samsung.android.sdk.pen.settingui.SpenSettingPenLayout;
45 |
46 | /**
47 | * Provides common operations for using Samsung S-Pen.
48 | */
49 | public class PenService implements View.OnClickListener, SpenColorPickerListener, SpenSettingEraserLayout.EventListener, SpenPageDoc.HistoryListener, SpenReplayListener, SpenZoomListener, SpenPenChangeListener, SpenEraserChangeListener, SpenPenDetachmentListener, SpenTouchListener {
50 | /**
51 | * The pen button for selecting the pen tool and showing the {@link SpenSettingPenLayout pen setting}.
52 | */
53 | public static final int BUTTON_PEN = 0;
54 |
55 | /**
56 | * The eraser button for selecting the eraser tool and showing the {@link SpenSettingEraserLayout eraser setting}.
57 | */
58 | public static final int BUTTON_ERASER = 1;
59 |
60 | /**
61 | * The undo button used to undo in history.
62 | */
63 | public static final int BUTTON_UNDO = 2;
64 |
65 | /**
66 | * The redo button used to redo in history.
67 | */
68 | public static final int BUTTON_REDO = 3;
69 |
70 | /**
71 | * The zoom button used to zoom the canvas in 1.5x.
72 | */
73 | public static final int BUTTON_ZOOM = 4;
74 |
75 | /**
76 | * Positions a background image at the center of the {@link SpenSurfaceView canvas}.
77 | */
78 | public static final int MODE_CENTER = SpenPageDoc.BACKGROUND_IMAGE_MODE_CENTER;
79 |
80 | /**
81 | * Fits a background image at the center of the {@link SpenSurfaceView canvas} and keep its aspect ratio.
82 | */
83 | public static final int MODE_FIT = SpenPageDoc.BACKGROUND_IMAGE_MODE_FIT;
84 |
85 | /**
86 | * Tiles a background image with the {@link SpenSurfaceView canvas}.
87 | */
88 | public static final int MODE_TILE = SpenPageDoc.BACKGROUND_IMAGE_MODE_TILE;
89 |
90 | /**
91 | * Stretches a background image to fill the {@link SpenSurfaceView canvas}.
92 | */
93 | public static final int MODE_STRETCH = SpenPageDoc.BACKGROUND_IMAGE_MODE_STRETCH;
94 |
95 | /**
96 | * Replay is paused.
97 | * @see #getReplayState()
98 | */
99 | public static final int REPLAY_STATE_PAUSED = SpenSurfaceView.REPLAY_STATE_PAUSED;
100 |
101 | /**
102 | * Replay is playing.
103 | * @see #getReplayState()
104 | */
105 | public static final int REPLAY_STATE_PLAYING = SpenSurfaceView.REPLAY_STATE_PLAYING;
106 |
107 | /**
108 | * Replay is stopped.
109 | * @see #getReplayState()
110 | */
111 | public static final int REPLAY_STATE_STOPPED = SpenSurfaceView.REPLAY_STATE_STOPPED;
112 |
113 | /**
114 | * Replays animation at a slow speed.
115 | */
116 | public static final int SPEED_SLOW = 0;
117 |
118 | /**
119 | * Replays animation at normal speed.
120 | */
121 | public static final int SPEED_NORMAL = 1;
122 |
123 | /**
124 | * Replays animation at a fast speed.
125 | */
126 | public static final int SPEED_FAST = 2;
127 |
128 | private static final String NULL = new String();
129 |
130 | private static final Uri SPEN_SDK_MARKET_URI = Uri.parse("market://details?id=" + Spen.SPEN_NATIVE_PACKAGE_NAME); //$NON-NLS-1$
131 |
132 | private static final int RESPONSE_TIME = 500;
133 |
134 | private static final float ZOOM_RATIO = 1.5f;
135 |
136 | private final Activity activity;
137 | private final View rootLayout;
138 |
139 | private SpenSurfaceView surfaceView;
140 | private SpenNoteDoc noteDoc;
141 |
142 | private SpenSettingPenLayout penSetting;
143 | private SpenSettingEraserLayout eraserSetting;
144 |
145 | private ToggleButton penButton;
146 | private ToggleButton eraserButton;
147 | private ToggleButton undoButton;
148 | private ToggleButton redoButton;
149 | private ToggleButton zoomButton;
150 |
151 | private boolean penEnabled;
152 | private boolean penSettingEnabled = true;
153 | private boolean eraserSettingEnabled = true;
154 | private int toolType = SpenSettingViewInterface.TOOL_SPEN;
155 | private int currentPage;
156 | private int canvasWidth;
157 | private boolean dirty;
158 | private boolean isZoomed;
159 |
160 | /**
161 | * Determines whether a SPD file is password protected.
162 | * @param path the absolute path of a SPD file.
163 | * @return true if the file is password protected; otherwise, false.
164 | */
165 | public static boolean isLocked(final String path) {
166 | return SpenNoteFile.isLocked(path);
167 | }
168 |
169 | /**
170 | * Protects a SPD file with the specified password.
171 | * @param context an application context.
172 | * @param path the absolute path of a SPD file.
173 | * @param password the password to protect a SPD file.
174 | * @throws IOException thrown if the specified path is not found, or if a temporary directory cannot be created.
175 | * @throws SpenUnsupportedTypeException thrown if the specified path is not a SPD file.
176 | */
177 | public static void lock(final Context context, final String path, final String password) throws IOException, SpenUnsupportedTypeException {
178 | SpenNoteFile.lock(context, path, password);
179 | }
180 |
181 | /**
182 | * Unprotects a SPD file using the given password.
183 | * @param context an application context.
184 | * @param path the absolute path of a SPD file.
185 | * @param password the password to unprotect a SPD file.
186 | * @throws IOException thrown if the specified path is not found, or if a temporary directory cannot be created.
187 | * @throws SpenUnsupportedTypeException thrown if the specified path is not a SPD file.
188 | * @throws SpenInvalidPasswordException thrown if the given password is incorrect.
189 | */
190 | public static void unlock(final Context context, final String path, final String password) throws IOException, SpenInvalidPasswordException, SpenUnsupportedTypeException {
191 | SpenNoteFile.unlock(context, path, password);
192 | }
193 |
194 | /**
195 | * Initializes tool buttons ({@link #BUTTON_PEN}, {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO})
196 | * and settings ({@link SpenSettingPenLayout pen setting} and {@link SpenSettingEraserLayout eraser setting}.
197 | * @param activity the {@link Activity} that hosts a {@link RelativeLayout}, where a {@link SpenSurfaceView canvas}
198 | * will be created.
199 | * @param rootLayout the {@link View} that contains {@link R.id.pen_container} and {@link R.id.pen_canvas}.
200 | */
201 | public PenService(final Activity activity, final View rootLayout) {
202 | this.activity = activity;
203 | this.rootLayout = rootLayout;
204 |
205 | final ViewGroup penButtons = (ViewGroup)rootLayout.findViewById(R.id.pen_buttons);
206 |
207 | if (penButtons != null) {
208 | this.penButton = (ToggleButton)penButtons.findViewById(R.id.pen_pen_button);
209 | this.eraserButton = (ToggleButton)penButtons.findViewById(R.id.pen_eraser_button);
210 | this.undoButton = (ToggleButton)penButtons.findViewById(R.id.pen_undo_button);
211 | this.redoButton = (ToggleButton)penButtons.findViewById(R.id.pen_redo_button);
212 | this.zoomButton = (ToggleButton)penButtons.findViewById(R.id.pen_zoom_button);
213 | }
214 | }
215 |
216 | /**
217 | * Cleans up any resources used by the Pen package.
218 | */
219 | public void onDestroy() {
220 | if (this.noteDoc != null) {
221 | for (int i = this.noteDoc.getPageCount(); --i >= 0;) {
222 | final SpenPageDoc pageDoc = this.noteDoc.getPage(i);
223 |
224 | if (pageDoc.isRecording()) {
225 | pageDoc.stopRecord();
226 | }
227 | }
228 | }
229 |
230 | if (this.penSetting != null) {
231 | this.penSetting.close();
232 | }
233 |
234 | if (this.eraserSetting != null) {
235 | this.eraserSetting.close();
236 | }
237 |
238 | if (this.surfaceView != null) {
239 | this.surfaceView.close();
240 | }
241 |
242 | if (this.noteDoc != null) {
243 | try {
244 | this.noteDoc.close();
245 | } catch (final IOException e) {
246 | Log.e(this.getClass().getName(), e.getMessage(), e);
247 | }
248 | }
249 | }
250 |
251 | /**
252 | * Called when any one of the following buttons is clicked.
253 | * {@link R.id.pen_pen_button}, {@link R.id.pen_earser_button}, {@link R.id.pen_undo_button}, {@link R.id.pen_redo_button}
254 | * @param view the button clicked. 255 | */ 256 | @Override 257 | public void onClick(final View view) { 258 | final int id = view.getId(); 259 | 260 | if (id == R.id.pen_pen_button) { 261 | this.selectButton(PenService.BUTTON_PEN); 262 | } else if (id == R.id.pen_eraser_button) { 263 | this.selectButton(PenService.BUTTON_ERASER); 264 | } else if (id == R.id.pen_undo_button) { 265 | this.undo(); 266 | } else if (id == R.id.pen_redo_button) { 267 | this.redo(); 268 | } else if (id == R.id.pen_zoom_button) { 269 | this.zoom(); 270 | } 271 | } 272 | 273 | @Override 274 | public boolean onTouch(final View view, final MotionEvent event) { 275 | return false; 276 | } 277 | 278 | /** 279 | * Called when a color is picked. 280 | * @param color the color that is picked. 281 | * @param x the x coordinate in pixels. 282 | * @param y the y coordinate in pixels. 283 | */ 284 | @Override 285 | public void onChanged(final int color, final int x, final int y) { 286 | if (this.penSettingEnabled) { 287 | if (this.surfaceView == null) { 288 | throw new IllegalStateException(); 289 | } 290 | 291 | final SpenSettingPenInfo info = this.penSetting.getInfo(); 292 | 293 | info.color = color; 294 | 295 | this.surfaceView.setPenSettingInfo(info); 296 | this.penSetting.setInfo(info); 297 | } 298 | } 299 | 300 | /** 301 | * Called when a {@link SpenSettingPenInfo pen setting} is changed. 302 | * @param info the changed {@link SpenSettingPenInfo pen setting}. 303 | */ 304 | @Override 305 | public void onChanged(final SpenSettingPenInfo info) { 306 | } 307 | 308 | /** 309 | * Called when a {@link SpenSettingEraserInfo eraser setting} is changed. 310 | * @param info the changed {@link SpenSettingEraserInfo eraser setting}. 311 | */ 312 | @Override 313 | public void onChanged(final SpenSettingEraserInfo info) { 314 | } 315 | 316 | /** 317 | * Called when the user requests to clear all objects on the {@link SpenSurfaceView canvas}. 318 | */ 319 | @Override 320 | public void onClearAll() { 321 | if (this.surfaceView == null) { 322 | throw new IllegalStateException(); 323 | } 324 | 325 | if (this.noteDoc != null) { 326 | this.noteDoc.getPage(this.currentPage).removeAllObject(); 327 | } 328 | 329 | this.surfaceView.update(); 330 | } 331 | 332 | /** 333 | * Called when a new history event is committed. 334 | * @param the current {@link SpenPageDoc page} that commits a new history. 335 | */ 336 | @Override 337 | public void onCommit(final SpenPageDoc doc) { 338 | this.dirty = true; 339 | } 340 | 341 | /** 342 | * Called when the undoable state is changed. 343 | * @param doc the current {@link SpenPageDoc page} with changed undoable state. 344 | * @param undoable the current undoable state. 345 | */ 346 | @Override 347 | public void onUndoable(final SpenPageDoc doc, final boolean undoable) { 348 | if (this.undoButton != null) { 349 | this.undoButton.setEnabled(undoable); 350 | } 351 | } 352 | 353 | /** 354 | * Called when the redoable state is changed. 355 | * @param doc the current {@link SpenPageDoc page} with changed redoable state. 356 | * @param redoable the current redoable state. 357 | */ 358 | @Override 359 | public void onRedoable(final SpenPageDoc doc, final boolean redoable) { 360 | if (this.redoButton != null) { 361 | this.redoButton.setEnabled(redoable); 362 | } 363 | } 364 | 365 | /** 366 | * Called when a replay is completed. 367 | */ 368 | @Override 369 | public void onCompleted() { 370 | } 371 | 372 | /** 373 | * Called when a replay is playing. 374 | * @param progress the progress indicator ranging from 0 to 100. 375 | * @param id the object ID. 376 | */ 377 | @Override 378 | public void onProgressChanged(final int progress, final int id) { 379 | } 380 | 381 | /** 382 | * Called when a canvas zoom is completed. 383 | * @param panX the x coordinate in pixels. 384 | * @param panY the y coordinate in pixels. 385 | * @param ratio the zoom ratio. 386 | */ 387 | @Override 388 | public void onZoom(final float panX, final float panY, final float ratio) { 389 | } 390 | 391 | /** 392 | * Called when S-Pen is detached from or attached to a device. 393 | * @param detachedtrue if S-Pen is detached; otherwise, false.
394 | */
395 | @Override
396 | public void onDetached(final boolean detached) {
397 | }
398 |
399 | /**
400 | * Determines if the {@link SpenNoteDoc document} is modified.
401 | * @return true if the {@link SpenNoteDoc document} is modified; otherwise, false.
402 | */
403 | public boolean isDirty() {
404 | if (this.noteDoc == null) {
405 | return this.dirty;
406 | }
407 |
408 | if (this.noteDoc.isChanged()) {
409 | return true;
410 | }
411 |
412 | return this.dirty;
413 | }
414 |
415 | /**
416 | * Determines whether a pen is available on the device.
417 | * @return true if a pen is available; otherwise, false.
418 | */
419 | public boolean isPenEnabled() {
420 | return this.penEnabled;
421 | }
422 |
423 | /**
424 | * Gets the current displayed page index.
425 | * @return the current displayed page index.
426 | */
427 | public int getCurrentPage() {
428 | return this.noteDoc == null ? -1 : this.currentPage;
429 | }
430 |
431 | /**
432 | * Sets the current page to display.
433 | * @param page the page index to display.
434 | */
435 | public void setCurrentPage(final int page) {
436 | if (this.surfaceView == null) {
437 | throw new IllegalStateException();
438 | }
439 |
440 | if (this.noteDoc != null) {
441 | if (this.currentPage != page) {
442 | this.currentPage = page;
443 |
444 | this.surfaceView.setPageDoc(this.noteDoc.getPage(this.currentPage), true);
445 | }
446 | }
447 | }
448 |
449 | /**
450 | * Sets the background color of a page.
451 | * @param page the index of the page to set color to.
452 | * @param color the color to set.
453 | */
454 | public void setBackgroundColor(final int page, final int color) {
455 | if (this.surfaceView == null) {
456 | throw new IllegalStateException();
457 | }
458 |
459 | if (this.noteDoc != null) {
460 | this.noteDoc.getPage(page).setBackgroundColor(color);
461 | }
462 | }
463 |
464 | /**
465 | * Sets the background image of a page.
466 | * @param page the index of the page to set color to.
467 | * @param imagePath the path of an image file.
468 | */
469 | public void setBackgroundImage(final int page, final String imagePath) {
470 | if (this.surfaceView == null) {
471 | throw new IllegalStateException();
472 | }
473 |
474 | if (this.noteDoc != null) {
475 | this.noteDoc.getPage(page).setBackgroundImage(imagePath);
476 | }
477 | }
478 |
479 | /**
480 | * Sets the visibility of any one of the following buttons:
481 | * {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
482 | * @param button one of {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
483 | * @param visibility one of {@link View#VISIBLE}, {@link View#INVISIBLE} and {@link View#GONE}.
484 | */
485 | public void setButtonVisibility(final int button, final int visibility) {
486 | View view = null;
487 |
488 | switch (button) {
489 | case BUTTON_PEN: view = this.penButton; break;
490 | case BUTTON_ERASER: view = this.eraserButton; break;
491 | case BUTTON_UNDO: view = this.undoButton; break;
492 | case BUTTON_REDO: view = this.redoButton; break;
493 | case BUTTON_ZOOM: view = this.zoomButton; break;
494 | }
495 |
496 | if (view != null) {
497 | view.setVisibility(visibility);
498 | }
499 | }
500 |
501 | /**
502 | * Sets a drawable resource for a button.
503 | * @param button one of {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
504 | * @param resource a drawable resource to set.
505 | */
506 | public void setButtonResource(final int button, final int resource) {
507 | this.setButtonDrawable(button, this.activity.getResources().getDrawable(resource));
508 | }
509 |
510 | /**
511 | * Sets a drawable for a button.
512 | * @param button one of {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
513 | * @param drawable a drawable to set.
514 | */
515 | public void setButtonDrawable(final int button, final Drawable drawable) {
516 | ToggleButton view = null;
517 |
518 | switch (button) {
519 | case BUTTON_PEN:
520 | if (this.penButton != null) {
521 | view = this.penButton;
522 | }
523 |
524 | break;
525 |
526 | case BUTTON_ERASER:
527 | if (this.eraserButton != null) {
528 | view = this.eraserButton;
529 | }
530 |
531 | break;
532 |
533 | case BUTTON_UNDO:
534 | if (this.undoButton != null) {
535 | view = this.undoButton;
536 | }
537 |
538 | break;
539 |
540 | case BUTTON_REDO:
541 | if (this.redoButton != null) {
542 | view = this.redoButton;
543 | }
544 |
545 | break;
546 |
547 | case BUTTON_ZOOM:
548 | if (this.zoomButton != null) {
549 | view = this.zoomButton;
550 | }
551 |
552 | break;
553 | }
554 |
555 | if (view != null) {
556 | view.setCompoundDrawables(null, drawable, null, null);
557 | }
558 | }
559 |
560 | /**
561 | * Determines whether {@link SpenSettingPenLayout pen setting} is enabled.
562 | * @return true if {@link SpenSettingPenLayout pen setting} is enabled; otherwise, false.
563 | */
564 | public boolean isPenSettingEnabled() {
565 | return this.penSettingEnabled;
566 | }
567 |
568 | /**
569 | * Enables or disables {@link SpenSettingPenLayout pen setting} when {@link #BUTTON_PEN} is clicked.
570 | * @param enabled true to enable {@link SpenSettingPenLayout pen setting}; otherwise, false.
571 | */
572 | public void setPenSettingEnabled(final boolean enabled) {
573 | this.penSettingEnabled = enabled;
574 | }
575 |
576 | /**
577 | * Determines whether {@link SpenSettingEraserLayout eraser layout} is enabled.
578 | * @return true if {@link SpenSettingEraserLayout eraser layout} is enabled; otherwise, false.
579 | */
580 | public boolean isEraserSettingEnabled() {
581 | return this.eraserSettingEnabled;
582 | }
583 |
584 | /**
585 | * Enables or disables {@link SpenSettingEraserLayout eraser setting} when {@link #BUTTON_ERASER} is clicked.
586 | * @param enabled true to enable {@link SpenSettingEraserLayout eraser setting}; otherwise, false.
587 | */
588 | public void setEraserSettingEnabled(final boolean enabled) {
589 | this.eraserSettingEnabled = enabled;
590 | }
591 |
592 | /**
593 | * Determines whether scroll feature is enabled.
594 | * @return true if scroll feature is enabled; otherwise, false.
595 | */
596 | public boolean isScrollEnabled() {
597 | if (this.surfaceView == null) {
598 | throw new IllegalStateException();
599 | }
600 |
601 | return this.surfaceView.isHorizontalSmartScrollEnabled() || this.surfaceView.isVerticalSmartScrollEnabled();
602 | }
603 |
604 | /**
605 | * Enables or disables "Smart Scroll" feature.
606 | * If enabled, the {@link SpenSurfaceView canvas} automatically scrolls when a S-Pen hovers over the edge of it.
607 | *To enable scroll feature, call this method after {@link #init(int, int, int, String, int, int, int, int)} 608 | * to ensure that the {@link SpenSurfaaceView canvas} layout is ready.
609 | * @param enabletrue to enable scroll feature, or false to disable it. Default is false.
610 | * @param edgeSize the size of each of the 4 edges in pixels
611 | * @param velocity the scroll velocity in pixels.
612 | */
613 | public void setScrollEnabled(final boolean enable, final int edgeSize, final int velocity) {
614 | if (this.surfaceView == null) {
615 | throw new IllegalStateException();
616 | }
617 |
618 | final int width = this.surfaceView.getWidth();
619 | final int height = this.surfaceView.getHeight();
620 |
621 | this.surfaceView.setHorizontalSmartScrollEnabled(enable, new Rect(0, 0, edgeSize, height), new Rect(width, 0, width, height), PenService.RESPONSE_TIME, velocity);
622 | this.surfaceView.setVerticalSmartScrollEnabled(enable, new Rect(0, 0, width, 0), new Rect(0, height, width, height), PenService.RESPONSE_TIME, velocity);
623 | }
624 |
625 | /**
626 | * Determines whether zoom feature is enabled.
627 | * @return true if zoom feature is enabled; otherwise, false.
628 | */
629 | public boolean isZoomEnabled() {
630 | if (this.surfaceView == null) {
631 | throw new IllegalStateException();
632 | }
633 |
634 | return this.surfaceView.isSmartScaleEnabled();
635 | }
636 |
637 | /**
638 | * Enables or disables "Smart Zoom" feature.
639 | * If enabled, the {@link SpenSurfaceView canvas} automatically zooms when a S-Pen hovers over it.
640 | *To enable zoom feature, call this method after {@link #init(int, int, int, String, int, int, int, int)} 641 | * to ensure that the {@link SpenSurfaceView canvas} layout is ready.
642 | *Note: This may not work on Android 4.0 (API level 14) devices.
643 | * @param enabletrue to enable zoom feature, or false to disable it. Default is false.
644 | * @param zoomRatio the zoom ratio.
645 | */
646 | public void setZoomEnabled(final boolean enable, final float zoomRatio) {
647 | if (this.surfaceView == null) {
648 | throw new IllegalStateException();
649 | }
650 |
651 | this.surfaceView.setSmartScaleEnabled(enable, new Rect(0, 0, this.surfaceView.getWidth(), this.surfaceView.getHeight()), 8, PenService.RESPONSE_TIME, zoomRatio);
652 | }
653 |
654 | /**
655 | * Select a tool button to use.
656 | * @param button one of {@link #BUTTON_PEN} {@link #BUTTON_ERASER}, {@link #BUTTON_UNDO} and {@link #BUTTON_REDO}.
657 | */
658 | public void selectButton(final int button) {
659 | if (this.surfaceView != null) {
660 | switch (button) {
661 | case BUTTON_PEN:
662 | if (this.penButton != null) {
663 | this.eraserSetting.setVisibility(View.GONE);
664 |
665 | this.surfaceView.setToolTypeAction(this.toolType, SpenSettingViewInterface.ACTION_STROKE);
666 |
667 | if (this.eraserButton != null) {
668 | this.eraserButton.setChecked(false);
669 | }
670 |
671 | if (!this.penButton.isChecked()) {
672 | this.penButton.setChecked(true);
673 | }
674 |
675 | this.onPenSettingClick();
676 | }
677 |
678 | break;
679 |
680 | case BUTTON_ERASER:
681 | if (this.eraserButton != null) {
682 | this.penSetting.setVisibility(View.GONE);
683 |
684 | if (this.penButton != null) {
685 | this.penButton.setChecked(false);
686 | }
687 |
688 | if (!this.eraserButton.isChecked()) {
689 | this.eraserButton.setChecked(true);
690 | }
691 |
692 | this.onEraserSettingClick();
693 | }
694 |
695 | break;
696 | }
697 | }
698 | }
699 |
700 | /**
701 | * Gets the number of pages.
702 | * @return the number of pages.
703 | */
704 | public int getPageCount() {
705 | if (this.noteDoc == null) {
706 | return 0;
707 | }
708 |
709 | return this.noteDoc.getPageCount();
710 | }
711 |
712 | /**
713 | * Appends a new {@link SpenPageDoc page} with the specified background color/image.
714 | * @param backgroundColor the background color of the {@link SpenPageDoc page}.
715 | * The default color will be used if backgroundColor is negative.
An empty background will be used if backgroundImagePath is empty or null.
The default color will be used if backgroundColor is negative.
An empty background will be used if backgroundImagePath is empty or null.
pageIndex to a new index.
776 | * @param pageIndex the page index to move from.
777 | * @param step the number of page index to move.
778 | * Moves a page forward by specifying a positive step, or backward by specifying a negative step.
779 | */ 780 | public void movePage(final int pageIndex, final int step) { 781 | if (this.noteDoc != null) { 782 | this.noteDoc.movePageIndex(this.noteDoc.getPage(pageIndex), step); 783 | } 784 | } 785 | 786 | /** 787 | * Undo the previous action, if any. 788 | */ 789 | public void undo() { 790 | if (this.surfaceView == null) { 791 | throw new IllegalStateException(); 792 | } 793 | 794 | if (this.noteDoc != null) { 795 | final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage); 796 | 797 | if (pageDoc.isUndoable()) { 798 | this.surfaceView.updateUndo(pageDoc.undo()); 799 | } 800 | } 801 | } 802 | 803 | /** 804 | * Redo the next action, if any. 805 | */ 806 | public void redo() { 807 | if (this.surfaceView == null) { 808 | throw new IllegalStateException(); 809 | } 810 | 811 | if (this.noteDoc != null) { 812 | final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage); 813 | 814 | if (pageDoc.isRedoable()) { 815 | this.surfaceView.updateRedo(pageDoc.redo()); 816 | } 817 | } 818 | } 819 | 820 | private void zoom() { 821 | this.isZoomed = !this.isZoomed; 822 | 823 | this.setZoomEnabled(this.isZoomed, PenService.ZOOM_RATIO); 824 | 825 | this.zoomButton.setChecked(!this.zoomButton.isChecked()); 826 | } 827 | 828 | /** 829 | * Starts recording changes. 830 | *By default, the {@link SpenPageDoc document) internally records any changes.
831 | */ 832 | public void startRecord() { 833 | if (this.noteDoc != null) { 834 | final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage); 835 | 836 | if (!pageDoc.isRecording()) { 837 | pageDoc.startRecord(); 838 | } 839 | } 840 | } 841 | 842 | /** 843 | * Stops recording changes. 844 | *This is a no-op if recording is not previously started.
845 | */ 846 | public void stopRecord() { 847 | if (this.noteDoc != null) { 848 | final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage); 849 | 850 | if (pageDoc.isRecording()) { 851 | pageDoc.stopRecord(); 852 | } 853 | } 854 | } 855 | 856 | /** 857 | * Starts replaying the objects drawn on the {@link SpenSurfaceView canvas}, if any. 858 | *You can replay animation by calling {@link #startRecord()}, {@link #stopRecord()}, 859 | * {@link #startReplay()}, {@link #pauseReplay()}, {@link #resumeReplay()} and {@link #stopReplay()} methods.
860 | *It is an no-op if {@link #startRecord()} there is no object on the {@link SpenSurfaceView canvas}, 861 | * or {@link #startRecord()} is not called before.
862 | * @see #startRecord() 863 | * @see #stopRecord() 864 | * @see #stopReplay() 865 | * @see #pauseReplay() 866 | * @see #resumeReplay() 867 | */ 868 | public void startReplay() { 869 | if (this.surfaceView == null) { 870 | throw new IllegalStateException(); 871 | } 872 | 873 | this.surfaceView.startReplay(); 874 | } 875 | 876 | /** 877 | * Stops replaying the objects drawn on the {@link SpenSurfaceView canvas}, if any. 878 | *You can replay animation by calling {@link #startRecord()}, {@link #stopRecord()}, 879 | * {@link #startReplay()}, {@link #pauseReplay()}, {@link #resumeReplay()} and {@link #stopReplay()} methods.
880 | *It is an no-op if {@link #startReplay()} is not called before.
881 | * @see #startRecord() 882 | * @see #stopRecord() 883 | * @see #startReplay() 884 | * @see #pauseReplay() 885 | * @see #resumeReplay() 886 | */ 887 | public void stopReplay() { 888 | if (this.surfaceView == null) { 889 | //throw new IllegalStateException(); 890 | } else { 891 | if (this.surfaceView.getReplayState() == SpenSurfaceView.REPLAY_STATE_PLAYING) { 892 | this.surfaceView.stopReplay(); 893 | } 894 | } 895 | } 896 | 897 | /** 898 | * Pauses replaying the objects drawn on the {@link SpenSurfaceView canvas}, if any. 899 | *You can replay animation by calling {@link #startRecord()}, {@link #stopRecord()}, 900 | * {@link #startReplay()}, {@link #pauseReplay()}, {@link #resumeReplay()} and {@link #stopReplay()} methods.
901 | *It is an no-op if {@link #startReplay()} is not called before.
902 | * @see #startRecord() 903 | * @see #stopRecord() 904 | * @see #startReplay() 905 | * @see #stopReplay() 906 | * @see #resumeReplay() 907 | */ 908 | public void pauseReplay() { 909 | if (this.surfaceView == null) { 910 | throw new IllegalStateException(); 911 | } 912 | 913 | this.surfaceView.pauseReplay(); 914 | } 915 | 916 | /** 917 | * Resumes replaying the objects drawn on the {@link SpenSurfaceView canvas}, if any. 918 | *You can replay animation by calling {@link #startRecord()}, {@link #stopRecord()}, 919 | * {@link #startReplay()}, {@link #pauseReplay()}, {@link #resumeReplay()} and {@link #stopReplay()} methods.
920 | *It is an no-op if {@link #pauseReplay()} is not called before.
921 | * @see #startRecord() 922 | * @see #stopRecord() 923 | * @see #startReplay() 924 | * @see #stopReplay() 925 | * @see #pauseReplay() 926 | */ 927 | public void resumeReplay() { 928 | if (this.surfaceView == null) { 929 | throw new IllegalStateException(); 930 | } 931 | 932 | this.surfaceView.resumeReplay(); 933 | } 934 | 935 | /** 936 | * Gets the animation replay state. 937 | * @return the current replay state. 938 | *Could be one of these values: {@link #REPLAY_STATE_PAUSED}, {@link #REPLAY_STATE_PLAYING} or {@link #REPLAY_STATE_STOPPED}.
939 | */ 940 | public int getReplayState() { 941 | if (this.surfaceView == null) { 942 | throw new IllegalStateException(); 943 | } 944 | 945 | return this.surfaceView.getReplayState(); 946 | } 947 | 948 | /** 949 | * Sets the speed for replaying objects drawn on the {@link SpenSurfaceView canvas}. 950 | * @param speed the replay speed. 951 | *The valid values are {@link #SPEED_SLOW}, {@link #SPEED_NORMAL} and {@link #SPEED_FAST}.
952 | */ 953 | public void setReplaySpeed(final int speed) { 954 | if (this.surfaceView == null) { 955 | throw new IllegalStateException(); 956 | } 957 | 958 | this.surfaceView.setReplaySpeed(speed); 959 | } 960 | 961 | /** 962 | * Creates a {@link Bitmap} from the current {@link SpenPageDoc page}. 963 | * @param scale the scale to resize the generated {@link Bitmap}. 964 | * @return the {@link Bitmap} captured from the current {@link SpenPageDoc page}. 965 | */ 966 | public Bitmap generateThumbnail(final float scale) { 967 | if (this.surfaceView == null) { 968 | throw new IllegalStateException(); 969 | } 970 | 971 | return this.surfaceView.capturePage(scale); 972 | } 973 | 974 | /** 975 | * Saves the {@link SpenNoteDoc document} to a SPD file at the specifiedpath.
976 | * @param path the absolute path to save a SPD file to.
977 | * @throws IOException if the operation fails.
978 | */
979 | public void save(final String path) throws IOException {
980 | if (this.dirty && this.noteDoc != null) {
981 | this.noteDoc.save(path);
982 | }
983 | }
984 |
985 | /**
986 | * Loads a SPD file into the current {@link SpenNoteDoc document}.
987 | * @param path the absolute path of a SPD file to load.
988 | * @param writable true if the SPD file should be writable; otherwise, false.
989 | * @throws IOException thrown if the specified path is not found or a cache directory cannot be generated.
990 | * @throws SpenUnsupportedTypeException thrown if the file to load is not in SPD format.
991 | * @throws SpenUnsupportedVersionException thrown if the Pen package installed on the device is incompatible.
992 | * @throws SpenInvalidPasswordException thrown if the file is password protected.
993 | */
994 | public void load(final String path, final boolean writable) throws SpenInvalidPasswordException, SpenUnsupportedTypeException, SpenUnsupportedVersionException, IOException {
995 | if (this.surfaceView == null) {
996 | throw new IllegalStateException();
997 | }
998 |
999 | final SpenNoteDoc noteDoc = new SpenNoteDoc(this.activity, path, this.canvasWidth, writable ? SpenNoteDoc.MODE_WRITABLE : SpenNoteDoc.MODE_READ_ONLY);
1000 |
1001 | if (this.noteDoc != null) {
1002 | this.noteDoc.close();
1003 | }
1004 |
1005 | this.noteDoc = noteDoc;
1006 |
1007 | if (this.noteDoc.getPageCount() > 0) {
1008 | this.currentPage = 0;
1009 |
1010 | final SpenPageDoc page = this.noteDoc.getPage(0);
1011 | page.setHistoryListener(this);
1012 |
1013 | this.surfaceView.setPageDoc(page, true);
1014 | this.surfaceView.update();
1015 | }
1016 |
1017 | if (this.noteDoc.getPageCount() > 1) {
1018 | this.noteDoc.getPage(1).setHistoryListener(this);
1019 | }
1020 | }
1021 |
1022 | /**
1023 | * Loads a SPD file into the current {@link SpenNoteDoc document}.
1024 | * @param path the absolute path of a SPD file to load.
1025 | * @param writable true if the SPD file should be writable; otherwise, false.
1026 | * @throws IOException thrown if the specified path is not found or a cache directory cannot be generated.
1027 | * @throws SpenUnsupportedTypeException thrown if the file to load is not in SPD format.
1028 | * @throws SpenUnsupportedVersionException thrown if the Pen package installed on the device is incompatible.
1029 | * @throws SpenInvalidPasswordException thrown if the specified password is incorrect.
1030 | */
1031 | public void load(final String path, final String password, final boolean writable) throws SpenInvalidPasswordException, SpenUnsupportedTypeException, SpenUnsupportedVersionException, IOException {
1032 | if (this.surfaceView == null) {
1033 | throw new IllegalStateException();
1034 | }
1035 |
1036 | final SpenNoteDoc noteDoc = new SpenNoteDoc(this.activity, path, password, this.canvasWidth, writable ? SpenNoteDoc.MODE_WRITABLE : SpenNoteDoc.MODE_READ_ONLY, !writable);
1037 |
1038 | if (this.noteDoc != null) {
1039 | this.noteDoc.close();
1040 | }
1041 |
1042 | this.noteDoc = noteDoc;
1043 |
1044 | if (this.noteDoc.getPageCount() > 0) {
1045 | this.currentPage = 0;
1046 |
1047 | final SpenPageDoc page = this.noteDoc.getPage(0);
1048 | page.setHistoryListener(this);
1049 |
1050 | this.surfaceView.setPageDoc(page, true);
1051 | this.surfaceView.update();
1052 | }
1053 |
1054 | if (this.noteDoc.getPageCount() > 1) {
1055 | this.noteDoc.getPage(1).setHistoryListener(this);
1056 | }
1057 | }
1058 |
1059 | /**
1060 | * Initialize S-Pen related objects.
1061 | * This will create a {@link SpenSurfaceView canvas} and a {@link SpenNoteDoc document} objects, 1062 | * and append a {@link SpenPageDoc page} to it.
1063 | *If S-Pen is not supported on the device, a message will be displayed and {@link Activity#finish()} will be called.
1064 | * @param canvasWidth the width of the {@link SpenSurfaceView canvas} to put a {@link SpenPageDoc page} in it. 1065 | * @param canvasHeight the height of the {@link SpenSurfaceView canvas} to put a {@link SpenPageDoc page} in it. 1066 | * @param backgroundColor the background color of the {@link SpenPageDoc page}. 1067 | *The default color will be used if backgroundColor is negative.
An empty background will be used if backgroundImagePath is empty or null.
true if Spen SDK is initialized successfully; otherwise, false.
1075 | */
1076 | 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) {
1077 | if (this.initSpen()) {
1078 | final RelativeLayout canvas = (RelativeLayout)this.rootLayout.findViewById(R.id.pen_canvas);
1079 |
1080 | if (this.penSettingEnabled) {
1081 | this.penSetting = new SpenSettingPenLayout(this.activity, PenService.NULL, canvas);
1082 | }
1083 |
1084 | if (this.eraserSettingEnabled) {
1085 | this.eraserSetting = new SpenSettingEraserLayout(this.activity, PenService.NULL, canvas);
1086 | }
1087 |
1088 | this.initCanvas(canvasWidth, canvasHeight, backgroundColor, backgroundImagePath, backgroundImageMode);
1089 | this.initPenInfo(penColor, penSize);
1090 | this.initEraserInfo(eraserSize);
1091 |
1092 | if (this.surfaceView != null) {
1093 | this.surfaceView.setColorPickerListener(this);
1094 | }
1095 |
1096 | if (this.eraserSetting != null) {
1097 | this.eraserSetting.setEraserListener(this);
1098 | }
1099 |
1100 | if (this.penButton != null) {
1101 | this.penButton.setOnClickListener(this);
1102 | }
1103 |
1104 | if (this.eraserButton != null) {
1105 | this.eraserButton.setOnClickListener(this);
1106 | }
1107 |
1108 | if (this.undoButton != null) {
1109 | this.undoButton.setOnClickListener(this);
1110 | }
1111 |
1112 | if (this.redoButton != null) {
1113 | this.redoButton.setOnClickListener(this);
1114 | }
1115 |
1116 | if (this.zoomButton != null) {
1117 | this.zoomButton.setOnClickListener(this);
1118 | }
1119 |
1120 | final SpenPageDoc pageDoc = this.noteDoc.getPage(this.currentPage);
1121 |
1122 | if (this.undoButton != null) {
1123 | this.undoButton.setEnabled(pageDoc.isUndoable());
1124 | }
1125 |
1126 | if (this.redoButton != null) {
1127 | this.redoButton.setEnabled(pageDoc.isRedoable());
1128 | }
1129 |
1130 | pageDoc.setHistoryListener(this);
1131 |
1132 | this.selectButton(PenService.BUTTON_PEN);
1133 | this.onPenSettingClick();
1134 |
1135 | return true;
1136 | }
1137 |
1138 | return false;
1139 | }
1140 |
1141 | private boolean initSpen() {
1142 | final Spen spen = new Spen();
1143 |
1144 | try {
1145 | spen.initialize(this.activity);
1146 |
1147 | this.penEnabled = spen.isFeatureEnabled(Spen.DEVICE_PEN);
1148 |
1149 | return true;
1150 | } catch (final SsdkUnsupportedException e) {
1151 | Log.i(this.getClass().getName(), e.getMessage(), e);
1152 |
1153 | this.handleUnsupportedException(e);
1154 | } catch (final Exception e) {
1155 | Toast.makeText(this.activity, R.string.message_spen_not_initialized, Toast.LENGTH_SHORT).show();
1156 |
1157 | this.activity.finish();
1158 | }
1159 |
1160 | return false;
1161 | }
1162 |
1163 | private void initCanvas(final int canvasWidth, final int canvasHeight, final int backgroundColor, final String backgroundImagePath, final int backgroundImageMode) {
1164 | final ViewGroup canvas = (ViewGroup)this.rootLayout.findViewById(R.id.pen_canvas);
1165 | final ViewGroup container = (ViewGroup)this.rootLayout.findViewById(R.id.pen_container);
1166 |
1167 | this.canvasWidth = canvasWidth;
1168 | this.surfaceView = new SpenSurfaceView(this.activity);
1169 |
1170 | canvas.addView(this.surfaceView);
1171 |
1172 | try {
1173 | this.noteDoc = new SpenNoteDoc(this.activity, canvasWidth, canvasHeight);
1174 | } catch (final IOException e) {
1175 | Toast.makeText(this.activity, R.string.message_spen_not_initialized, Toast.LENGTH_SHORT).show();
1176 |
1177 | this.activity.finish();
1178 | } catch (final Exception e) {
1179 | Toast.makeText(this.activity, R.string.message_spen_not_initialized, Toast.LENGTH_SHORT).show();
1180 |
1181 | this.activity.finish();
1182 | }
1183 |
1184 | if (this.noteDoc != null) {
1185 | final SpenPageDoc pageDoc = this.noteDoc.appendPage();
1186 | pageDoc.setBackgroundColor(backgroundColor);
1187 |
1188 | if (!TextUtils.isEmpty(backgroundImagePath)) {
1189 | pageDoc.setBackgroundImage(backgroundImagePath);
1190 | }
1191 |
1192 | if (backgroundImageMode == PenService.MODE_CENTER || backgroundImageMode == PenService.MODE_FIT || backgroundImageMode == PenService.MODE_TILE || backgroundImageMode == PenService.MODE_STRETCH) {
1193 | pageDoc.setBackgroundImageMode(backgroundImageMode);
1194 | }
1195 |
1196 | pageDoc.clearHistory();
1197 | pageDoc.setHistoryListener(this);
1198 |
1199 | this.surfaceView.setPageDoc(pageDoc, true);
1200 |
1201 | if (this.penEnabled) {
1202 | this.surfaceView.setToolTypeAction(SpenSettingViewInterface.TOOL_FINGER, SpenSettingViewInterface.ACTION_NONE);
1203 |
1204 | this.toolType = SpenSettingViewInterface.TOOL_SPEN;
1205 | } else {
1206 | this.surfaceView.setToolTypeAction(SpenSettingViewInterface.TOOL_FINGER, SpenSettingViewInterface.ACTION_STROKE);
1207 |
1208 | this.toolType = SpenSettingViewInterface.TOOL_FINGER;
1209 |
1210 | Toast.makeText(this.activity, R.string.message_finger_only, Toast.LENGTH_SHORT).show();
1211 | }
1212 | }
1213 |
1214 | if (this.penSettingEnabled) {
1215 | container.addView(this.penSetting);
1216 | this.penSetting.setCanvasView(this.surfaceView);
1217 | }
1218 |
1219 | if (this.eraserSettingEnabled) {
1220 | container.addView(this.eraserSetting);
1221 | this.eraserSetting.setCanvasView(this.surfaceView);
1222 | }
1223 |
1224 | this.surfaceView.setReplayListener(this);
1225 | this.surfaceView.setZoomListener(this);
1226 | this.surfaceView.setPenChangeListener(this);
1227 | this.surfaceView.setEraserChangeListener(this);
1228 | this.surfaceView.setPenDetachmentListener(this);
1229 | this.surfaceView.setTouchListener(this);
1230 | }
1231 |
1232 | private void initPenInfo(final int penColor, final int penSize) {
1233 | if (this.penSettingEnabled) {
1234 | if (this.surfaceView == null) {
1235 | throw new IllegalStateException();
1236 | }
1237 |
1238 | final SpenSettingPenInfo info = new SpenSettingPenInfo();
1239 |
1240 | info.color = penColor;
1241 | info.size = penSize;
1242 |
1243 | this.surfaceView.setPenSettingInfo(info);
1244 | this.penSetting.setInfo(info);
1245 | }
1246 | }
1247 |
1248 | private void initEraserInfo(final int eraserSize) {
1249 | if (this.eraserSettingEnabled) {
1250 | if (this.surfaceView == null) {
1251 | throw new IllegalStateException();
1252 | }
1253 |
1254 | final SpenSettingEraserInfo info = new SpenSettingEraserInfo();
1255 |
1256 | info.size = eraserSize;
1257 |
1258 | this.surfaceView.setEraserSettingInfo(info);
1259 | this.eraserSetting.setInfo(info);
1260 | }
1261 | }
1262 |
1263 | private void onPenSettingClick() {
1264 | if (this.penSettingEnabled) {
1265 | if (this.penSetting.isShown()) {
1266 | this.penSetting.setVisibility(View.GONE);
1267 | } else {
1268 | this.penSetting.setViewMode(SpenSettingPenLayout.VIEW_MODE_EXTENSION);
1269 |
1270 | final View toolsLayout = (View)this.rootLayout.findViewById(R.id.pen_buttons).getParent();
1271 | final FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)this.penSetting.getLayoutParams();
1272 | params.leftMargin = (int)toolsLayout.getX() + this.penButton.getLeft();
1273 | params.topMargin = (int)toolsLayout.getY() + this.penButton.getTop() + this.penButton.getHeight();
1274 |
1275 | this.penSetting.setLayoutParams(params);
1276 | this.penSetting.setVisibility(View.VISIBLE);
1277 | }
1278 | }
1279 | }
1280 |
1281 | private void onEraserSettingClick() {
1282 | if (this.eraserSettingEnabled) {
1283 | if (this.surfaceView.getToolTypeAction(this.toolType) == SpenSettingViewInterface.ACTION_ERASER) {
1284 | if (this.eraserSetting.isShown()) {
1285 | this.eraserSetting.setVisibility(View.GONE);
1286 | } else {
1287 | this.eraserSetting.setViewMode(SpenSettingEraserLayout.VIEW_MODE_NORMAL);
1288 |
1289 | final View toolsLayout = (View)this.rootLayout.findViewById(R.id.pen_buttons).getParent();
1290 | final FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)this.eraserSetting.getLayoutParams();
1291 | params.leftMargin = (int)toolsLayout.getX() + this.eraserButton.getLeft();
1292 | params.topMargin = (int)toolsLayout.getY() + this.eraserButton.getTop() + this.eraserButton.getHeight();
1293 |
1294 | this.eraserSetting.setLayoutParams(params);
1295 | this.eraserSetting.setVisibility(View.VISIBLE);
1296 | }
1297 | } else {
1298 | this.surfaceView.setToolTypeAction(this.toolType, SpenSettingViewInterface.ACTION_ERASER);
1299 | }
1300 | }
1301 | }
1302 |
1303 | private void handleUnsupportedException(final SsdkUnsupportedException e) {
1304 | int messageId = R.string.message_spen_not_initialized;
1305 | boolean isFatal = false;
1306 |
1307 | switch (e.getType()) {
1308 | case SsdkUnsupportedException.VENDOR_NOT_SUPPORTED:
1309 | messageId = R.string.message_non_samsung_device;
1310 | isFatal = true;
1311 |
1312 | break;
1313 |
1314 | case SsdkUnsupportedException.DEVICE_NOT_SUPPORTED:
1315 | messageId = R.string.message_spen_not_found;
1316 | isFatal = true;
1317 |
1318 | break;
1319 |
1320 | case SsdkUnsupportedException.LIBRARY_NOT_INSTALLED:
1321 | messageId = R.string.message_spen_library_not_found;
1322 |
1323 | break;
1324 |
1325 | case SsdkUnsupportedException.LIBRARY_UPDATE_IS_REQUIRED:
1326 | case SsdkUnsupportedException.LIBRARY_UPDATE_IS_RECOMMENDED:
1327 | messageId = R.string.message_spen_library_update_required;
1328 |
1329 | break;
1330 | }
1331 |
1332 | if (isFatal) {
1333 | Toast.makeText(this.activity, messageId, Toast.LENGTH_SHORT).show();
1334 |
1335 | this.activity.finish();
1336 | } else {
1337 | 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() {
1338 | @SuppressWarnings("synthetic-access")
1339 | @Override
1340 | public void onClick(final DialogInterface dialog, final int which) {
1341 | 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));
1342 |
1343 | dialog.dismiss();
1344 |
1345 | PenService.this.activity.finish();
1346 | }
1347 | }).setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
1348 | @SuppressWarnings({"synthetic-access"})
1349 | @Override
1350 | public void onClick(final DialogInterface dialog, final int which) {
1351 | dialog.dismiss();
1352 |
1353 | PenService.this.activity.finish();
1354 | }
1355 | }).show();
1356 | }
1357 | }
1358 | }
1359 |
--------------------------------------------------------------------------------