HTC Scribe

HTC Scribe Pen API Sample Code

The following code snippets based on the pen sample code demos illustrate the ability to use the pen and control its properties as well as setting up a painting drawing view in various scenarios. The complete source code is available as part of the SDK download.

Pen Demos

There are seven sample code projects included with the HTC OpenSense Tablet SDK Add-on: (see Advanced Sample Code section for additional sample projects)

HelloPen - minimal code required to create a pen app using the full HTC Pen framework

PenMenu - illustrates the use of the system wide Pen menu control.

HtcScrollPainting - illustrates use of stroke groups, zooming and panning beyond the visible screen.

PenEventDemo - illustrates how to use the low level pen events and buttons when providing your own source code for drawing (sample included) without using the full Pen framework.

SingleAPKDemo - demonstrates how to support both pen enabled devices and no-pen enabled devices within the same apk.

PenEventCompatibilityDemo - an alternate version of the PenEvent sample code that illustrates compatibility with the emulator and non-pen enabled devices using the same drawing code. This code includes a pencompatibility distributable jar file and doesn't require targeting the HTC add-on.

PenTouchPaintDemo - This is another compatibility example that also uses the pencompatibility jar file and also can simply target Android API Level 15. This is a port of Google's APIDemo that illustrates how to use the new ICS (Andorid 4.0) low level Pen API in a compatible way that also runs on Honeycomb (Android 3.0) devices.

Hello Pen Demo

The Hello Pen is the cannonical Hello World using the full HTC Scribe Pen Framework. This demonstrates the minimal coding required to get you up and running with a HtcPaintingView. It demonstrates where in the main layout xml you can change backgrounds and how you can easily code your first game, e.g. tic tac toe.

The following is essentially the entire code in the onCreate method, there are basically only 3 steps you have to add.
  1. Add HtcPaintingView to your layout and activity
  2. Initialize HtcPaintingView with a SerializeDAO
  3. Set group IDs and ViewPorts on painting view


Eclipse screenshot with collapsed code illustrating the minimal code required:

image


PenMenu Demo

image

In this demo we are using the PenMenu control, providing a drawing area to the right of it and 2 spinner controls (list of choices) for both the pen type and which actions to provide within the pen menu control. A button provides the ability to show or hide the PenMenu control.

The main layout, main.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    
android:orientation="vertical"
    
android:layout_width="fill_parent"
    
android:layout_height="fill_parent"
    
android:background="#665588"
    
>
    <
TextView
        android
:layout_width="match_parent"
        
android:layout_height="wrap_content"
        
android:text="@string/spinner_pen_set"
    
/>
    <
Spinner android:id="@+id/select_pen_set" 
        
android:layout_width="fill_parent"
        
android:layout_height="wrap_content">
    </
Spinner>
    
    <
TextView
        android
:layout_width="match_parent"
        
android:layout_height="wrap_content"
        
android:text="@string/spinner_action_button"
    
/>
    <
Spinner android:id="@+id/disable_action_button" 
        
android:layout_width="fill_parent"
        
android:layout_height="wrap_content">
    </
Spinner>
    
    <
Button android:id="@+id/queryAlpha"
        
android:layout_width="fill_parent"
        
android:layout_height="wrap_content"
        
android:text="@string/query_alpha">
    </
Button>
    
    <
FrameLayout
        android
:layout_width="fill_parent"
        
android:layout_height="fill_parent"
    
>
        <
com.htc.painting.engine.HtcPaintingView
            android
:id="@+id/paintingView" 
            
android:layout_width="match_parent"
            
android:layout_height="match_parent"     
        
/>
        <
FrameLayout
            android
:layout_gravity="bottom|right"
            
android:layout_width="fill_parent"
            
android:layout_height="wrap_content"
        
>
            <
com.htc.painting.penmenu.PenMenu
                android
:id="@+id/pen_menu"           
                
android:layout_width="match_parent" 
                
android:layout_height="match_parent" 
            
/>
        </
FrameLayout>
    </
FrameLayout>
</
LinearLayout

In MainActivity.java:

public void onCreate(Bundle savedInstanceState{
    super
.onCreate(savedInstanceState);
    
setContentView(R.layout.main);        
    
init();
  
}
    
  
public void init() {
    penMenu 
= (PenMenu)findViewById(R.id.pen_menu);
    
paintingView = (HtcPaintingView)findViewById(R.id.paintingView);
      
    
// Initialize the ink overlay (Htc Painting View)
    
HtcPaintingViewPort mHtcPaintingViewPort = new HtcPaintingViewPort();
    
paintingView.setEnabled(true);
    
FileSerializeDAO dao = new FileSerializeDAO(null);
    
HtcPaintingViewPort viewPort[] {mHtcPaintingViewPort};
    
int group[] {0};
    
paintingView.init(dao);
    
paintingView.requestStrokeGroups(groupviewPort);
      
    
// set painting view to pen menu
    
penMenu.setPaintingView(paintingView);
      
    
// testing UI to select pen set
    
selectPenSet = (SpinnerfindViewById(R.id.select_pen_set);
    
ArrayAdapter<CharSequenceadapter 
     
=ArrayAdapter.createFromResource(thisR.array.pen_setandroid.R.layout.simple_spinner_item);
    
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    
selectPenSet.setAdapter(adapter);
    
selectPenSet.setOnItemSelectedListener(
        new 
OnItemSelectedListener() {
            
public void onItemSelected(
                
AdapterView<?parentView viewint positionlong id{
                
if (id >= && id <= 2{
                    mCurrentPenSet 
position;
                
else {
                    mCurrentPenSet 
0;
                
}
                
// React the UI customized change when showing PenMenu next time
                
penMenu.hide();
                
// React the UI customized change when showing PenMenu next time
                
penMenu.setPenSetType(mCurrentPenSet);                      
            
}
            
public void onNothingSelected(AdapterView<?parent{
            }
        }
); 
          .... 

The PenMenu and PaintingView are retrieved from the layout, the PaintingView attached to the PenMenu and initialized with a file serializable data access object which is defined in a separate class and in this example a placeholder used for saving your drawings to a file.

A dropdown spinner resource is used to select a pen from a pen set, and its adapter and listener setup is shown above. The additional spinner and button left out from the listing above is also setup in onCreate().

Saving instance state using a bundle:

protected void onRestoreInstanceState(Bundle savedInstanceState{
    mCurrentPenSet 
savedInstanceState.getInt("penset"0);
    
selectPenSet.setSelection(mCurrentPenSet);
    
mActionButtonID savedInstanceState.getInt("action"0);
    
selectActionButton.setSelection(mActionButtonID);
    
penMenu.OnRestoreInstanceState(savedInstanceState);
  
}

  
@Override
  
protected void onSaveInstanceState(Bundle outState{
    outState
.putInt("action"mActionButtonID);
    
outState.putInt("penset"mCurrentPenSet);    
    
penMenu.OnSaveInstanceState(outState);
  

Handling the touch events:

public boolean dispatchTouchEvent(MotionEvent event{   
    boolean isPen 
PenEvent.isPenEvent(event);
    if (!
isPen{
      
return super.dispatchTouchEvent(event);
    
}
    int action 
PenEvent.PenAction(event);
    
int buttonStates PenEvent.PenButton(event);
    switch (
buttonStates{
      
case PenEvent.PEN_BUTTON_NONE:
        
penMenu.setEraserMode(false);
        
penMenu.forceHideStroke(false);
        break;

      case 
PenEvent.PEN_BUTTON2:        
        if (
action == PenEvent.PEN_ACTION_DOWN{        
          penMenu
.setEraserMode(true);
        
}
        
else if (action == PenEvent.PEN_ACTION_UP{
          penMenu
.setEraserMode(false);
        
}
        
return super.dispatchTouchEvent(event);
        
      case 
PenEvent.PEN_BUTTON1:
        if (
action == PenEvent.PEN_ACTION_DOWN)
          
penMenu.forceHideStroke(true);
        else if (
action == PenEvent.PEN_ACTION_UP)
          
penMenu.forceHideStroke(false);
        return 
true;
      
}
      
return super.dispatchTouchEvent(event);
    

PenEvent.isPenEvent(event) is used to determine if the MotionEvent is a pen event and then handled using the predefined pen event actions. The action is used to set the corresponding mode on the pen menu. Likewise we can handle shortcut keys by passing the KeyEvent to the pen menu's handler.

public boolean dispatchKeyEventKeyEvent event){
    int keyCode 
event.getKeyCode();
    if(
penMenu.handleKeyEvent(keyCodeevent) == true{
      
return true;
    
}
    
return super.dispatchKeyEvent(event);
  

This example lays the ground work for effectively using the PenMenu controller, in this next example we could've also used the PenMenu again for selecting pen attributes but we will demonstrate how to use framework to implement a custom PenMenu equivalent as shown.

Pen Scroll Painting Demo

image

In this demo we'll add an eraser, but more importantly the ability to draw on a view that can scroll off the view port off of the screen. Additionally we'll show how to set properties without the pen menu by creating your own custom dialogs and choice lists using a standard android system menu that can also be adapted for use as the applications' Android 3.1 Options style settings.

In HtcScrollPaintingActivity.java:

Note that HtcScrollPaintingActivity implements ColorPickerDialog.OnColorChangedListener and OnCacheListener.

public void onCreate(Bundle savedInstanceState{
    super
.onCreate(savedInstanceState);

    
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
        
WindowManager.LayoutParams.FLAG_FULLSCREEN);
    
setContentView(R.layout.main);
    
mFileSerializeDAO = new FileSerializeDAO(this);
    
    
mScrollView = (MyScrollViewthis.findViewById(R.id.ScrollView01);
    
mHtcPaintingView = (HtcPaintingViewfindViewById(R.id.gestures);
    
mHtcPaintingView.init(mFileSerializeDAO);
    
mProperties = new StrokeProperties();
  
    
setScrollListener();
    
mHtcPaintingView.setOnCacheListener(this);
    
initiDrawerGroup();
    
getPenList();
    
    
mHtcPaintingView.setDrawingPartialInvalidate(true);
    
mHtcPaintingView.setInkDrawingCacheRatio(1.0f2.0f);
    
mHtcPaintingView.setDocViewPort(mStrokeGroupViwePortTable.get(0));
    
mHtcPaintingView.setSeparatorDist(500);
  

Note that once again we setup our view, this time with some additional calls such as to setup invalidation (because of scrolling), the viewport, and additional items like a cache listener.

In MyScrollView.java:

protected void onScrollChanged(int lint tint oldlint oldt{
    super
.onScrollChanged(ltoldloldt);
    if (
mListener != null)
      
mListener.onScrollChange(ltoldloldt);
  
}
  
public static interface OnScrollListener {
    void onScrollChange
(int lint tint oldlint oldt);
  

Note that MyScrollView extends ScrollView so we're only adding listeners that we need, the functionality is already there.

One more thing we haven't covered is out instance of a ViewPort which provide the actual Rectangle we are currently viewing and drawing on, i.e. the visible region.

public class HtcScrollPaintingViewPort extends ViewPort {

    
private WeakReference<ScrollViewmAppScrollView null;
    
//visible region with coordinate based on the document's origin
    
private RectF mVisibleRegion null;
    private 
int mGroupId = -1;

    public 
HtcScrollPaintingViewPort(ScrollView svRectF visiableRegion,
        
int groupId{
      mAppScrollView 
= new WeakReference<ScrollView>(sv);
      
mVisibleRegion visiableRegion;
      
mGroupId groupId;
    

In the code you may have also noticed that this example reuses the volume keys so that we can also zoom in and out on our painting view. Hopefully all the parts needed for a fully functional drawing application - see additional comments in the source for additional notes on what's missing.

PenEvent Demo

As described in the overview this is a simpler example in that it doesn't using the full painting framework packages and just uses the low level API found in the PenEvent class. This isn't the preferred API to use since you would have to re-implement the drawing implementation of the pen API framework for a fully featured drawing application, however it's provided for those only interested in the low level pen events (e.g. for porting existing code)..

In this example we are creating a simple drawing application with all the drawing plumbing within the example using just simple canvas drawing methods. The pen's two buttons are used,The first one to call up a color pallete, the second to erase. Note that a best practice is to provide am alternate way to access the functionality that the buttons provide. This example also demonstrates how to handle both touch and pen events in the same onTouchEvent method. In this case, touch events also erase, which means you can use the back of the pen to erase as well! Also illustrated is pen pressure to set line wisth, using MotionEvent's getPressure() method.

From PenEventDemoActivity.java:

public boolean onTouchEvent(MotionEvent event{

            
// handle pen event:
            
if (PenEvent.isPenEvent(event)) {
                int action 
PenEvent.PenAction(event);
                
int button PenEvent.PenButton(event);
                
currentWidth 10 event.getPressure();
                if (
checkPenButtons(buttonevent)) {
                    
return true;
                
}
                
switch (action{
                    
case PenEvent.PEN_ACTION_DOWN:
                        
handleAction(ACTION_DOWNevent.getX(), event.getY());
                        break;
                    case 
PenEvent.PEN_ACTION_UP:
                        
handleAction(ACTION_UPevent.getX(), event.getY());
                        break;
                    case 
PenEvent.PEN_ACTION_MOVE:
                        
handleAction(ACTION_MOVEevent.getX(), event.getY());
                        break;
                
}
            } 
else {
                
// handle touch event
                
handleErase(event);
            
}
            
return true;
        
}

    
public void handleErase(MotionEvent event{
        
for(int i 0pointsX.size(); i++) {
            Float x 
pointsX.get(i);
            
Float y pointsY.get(i);
            if ( 
null != && null != 
                    
&& Math.abs(x-event.getX()) < ERASE_RADIUS 
                    
&& Math.abs(y-event.getY()) < ERASE_RADIUS{
                pointsX
.set(inull);
                
pointsY.set(inull);
            
}             
        }
    }

        
public void handleAction(int actionfloat xfloat y{
            synchronized 
(renderer.getSurfaceHolder()) {
                
if (action == ACTION_DOWN{
                    pointsX
.add(null);
                    
pointsY.add(null);
                    
colors.add(currentColor);
                    
widths.add(currentWidth);
                
}
                pointsX
.add(x);
                
pointsY.add(y);
                
colors.add(currentColor);
                
widths.add(currentWidth);
            
}
        }

        
private boolean checkPenButtons(int penBtnMotionEvent event{
            
switch (penBtn{
                
case PenEvent.PEN_BUTTON_NONE:
                    
// Do nothing.
                    
break;
                case 
PenEvent.PEN_BUTTON1:
                    if (!
isShowingColorPicker{
                        Dialog colorPicker 
= new ColorPickerDialog(
                            
PenEventCompatibilityDemoActivity.this
                            
PenEventCompatibilityDemoActivity.thispaint.getColor());
                        
colorPicker.setOnDismissListener(new OnDismissListener() {
                            
@Override
                            
public void onDismiss(DialogInterface dialog{
                                isShowingColorPicker 
false;
                            
}
                        }
);
                        
isShowingColorPicker true;
                        
colorPicker.show(); 
                    
}
                
case PenEvent.PEN_BUTTON2:
                    
handleErase(event);
                    return 
true;
            
}
            
return false;
        
}

        
@Override
        
public void onDraw(Canvas canvas{
            canvas
.drawColor(Color.WHITE);
            
Float lastX null;
            
Float lastY null;
            for (
int i 0pointsX.size(); i++) {
                Float x 
pointsX.get(i);
                
Float y pointsY.get(i);
                if (
null != lastX && null != lastY && null != && null != y{
                    paint
.setColor(colors.get(i));
                    
paint.setStrokeWidth(widths.get(i));
                    
canvas.drawLine(lastXlastYxypaint);
                
}
                lastX 
x;
                
lastY y;
            
}
        } 

So basically after we determine the MotionEvent is a PenEvent, we retrieve the PenAction and PenButton, and we handle those in corresponding methods as well as store the pressure level.

PenEvent Compatibility Demo

PenEventCompatibilityDemo and PenEventDemo are identical except for the following code changes which require the accompanying compatibility jar file:

Included is the PenEventCompatibilty jar library to be packaged with your application should you want to write a low level PenEvent application that supports both Android 3.1 and Android 2.3 as well as support for non-pen enabled devices such as the emulator.

Note: This library jar file must be added to build the project in Eclipse. From Project Properties -> Java Build Path -> Libraries -> Add Jars..., select the PenEventCompatibilty.jar file from the project's libs folder. If you imported the sample code into Eclipse, this is already taken care of.

The following are the only changes made to the PenEventDemo sample:

  1. 1) import com.htc.pen.compat.PenEvent instead of com.htc.pen.PenEvent
  2. 2) extend PenEventActivity instead of Activty
  3. 3) add registerView(View view) in onCreate()
    - note: this 3rd item is only for 3.x support if the drawing view not set in setContentView()
import com.htc.pen.compat.PenEvent;
import com.htc.pen.compat.PenEventActivity

public class 
PenEventDemoCompatActivity extends PenEventActivity {
    
private SketchPad pad;

    @
Override
    
public void onCreate(Bundle savedInstanceState{
        super
.onCreate(savedInstanceState);
        
pad = new SketchPad(this);
        
setContentView(pad);
        
// only required for GB compatibility if view not set in setContentView()
        //registerView(pad);
    

Optionally, again just as part of this compatibility example, there is the option to support the pen menu key with the additional methods: onPenMenuDown and onPenMenuUp. PenMenu support by default allows the system wide pen menu default support to be displayed just as with the PenEventDemo example.

The drawing code doesn't change, and this should work (again limited to just the low level API) on both Android 3.1 (Honeycomb) and Android 2.3.x (Gingerbread) devices that support the scribe pen. Emulator support is limited to just this compatibility example, use 'P' or 'Q' keys to indicate the pen touching the screen and '1' and '2' keys for the 2 buttons on the pen and 'M" for the optional onMenuKeyDown/Up support.

Single APK Demo

image

This example demonstrates how to provide pen API functionality for pen enabled devices as well as support for non-pen enabled devices using the same codebase and providing only one (a single) APK application file. In this example we add pen functionality as a separate activity and provide the option to use it to the user at runtime by enabling the appropriate button. For illustrating both options, two buttons are shown in this demo for both pen enabled and non-pen enabled devices. In practice, one of the buttons would probably be set to be invisible rather than disabled. Since the activities load the classes at runtime, one can include all the code to support both cases.

The following code illustrates one way to detect whether the device supports the HTC Scribe Pen at runtime:

public class PenFeatureDetector {

    
/**
     * Accesses methods that exist only on Android API level 5 or later.
     */
    
private static class SDK5Operations {
        
        
/**
         * @param context Context used to access PackageManager
         * @return true if running on an Android device with the pen hardware feature, false otherwise
         */
        
private static boolean hasPenFeature(final Context context{
            
final PackageManager pm context.getPackageManager();
            for (final 
FeatureInfo feature pm.getSystemAvailableFeatures()) {
                
if (PEN_FEATURE_NAME.equals(feature.name)) {
                    
return true;
                
}
            }
            
return false;
        
}
    }

    
/**
     * The name of a class used in the HTC Scribe pen API.
     */
    
private static final String PEN_EVENT_CLASS_NAME "com.htc.pen.PenEvent";

    
/**
     * The name of the Android feature that indicates a pen.
     */
    
private static final String PEN_FEATURE_NAME "android.hardware.touchscreen.pen";

    
/**
     * @param context Context used to access PackageManager
     * @return true if running on a device with the HTC Scribe pen supported, false otherwise
     */
    
public static boolean hasPenEvent(final Context context{
        
return hasHtcPenEventClass() && hasPenFeature(context);
    
}

    
/**
     * @return true if HTC PenEvent class to access pen data is available, false otherwise
     */
    
private static boolean hasHtcPenEventClass() {
        
try {
            
Class.forName(PEN_EVENT_CLASS_NAME);
        
catch (final ClassNotFoundException e{
            
return false;
        
}
        
return true;
    
}

    
/**
     * @param context Context used to access PackageManager
     * @return true if running on an Android device with the pen hardware feature, false otherwise
     */
    
private static boolean hasPenFeature(final Context context{
        
// Return false if the version of Android doesn't have the needed method.
        
if (Integer.parseInt(Build.VERSION.SDK) < 5{
            
return false;
        
}
        
// Otherwise check for the feature.
        
return SDK5Operations.hasPenFeature(context);
    
}



PenTouchPaintDemo


This project illustrates another compatibility example using the pen compatibility jar library. In this case we are porting the ICS (Android 4.0) demo found in Google's APIDemos which uses the new standard ICS low level Pen API.

With minor changes (MotionEventCompat in place of MotionEvent) the code can be made to work in HoneyComb (Android 3.x) devices and as before can call back to run in the emulator (non-pen enabled devices).

As has been the case with the PenEventCompatibility demo example, you can easily target Android API Level 15 and not have to target the HTC OpenSense SDK add-on.


image