Showing posts with label source. Show all posts
Showing posts with label source. Show all posts

Saturday, October 8, 2016

Constraint Layout: Icon Label Text

I am making one of those classic layouts: an icon with two lines of text. I would like to use vector drawable for the icon, and scale it according to the text sizes. I want the top edge of the icon to line up with the top edge of the first line of text, and the bottom edge of the icon to line up with the bottom edge of the second line.

How would I do that? With ConstraintLayout!

<ImageView
  android:id="@+id/flower_image"
  android:layout_width="0dp"
  android:layout_height="0dp"
  app:layout_constraintTop_toTopOf="@+id/label"
  app:layout_constraintBottom_toBottomOf="@+id/text"
  app:layout_constraintLeft_toLeftOf="parent"
  app:layout_constraintDimensionRatio="1:1"
  app:srcCompat="@drawable/ic_flower"/>
<TextView
  android:id="@+id/label"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:layout_constraintLeft_toRightOf="@+id/image"
  app:layout_constraintTop_toTopOf="parent"
  android:text="@string/flower"
  android:textSize="16sp"/>
<TextView
  android:id="@+id/text"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  app:layout_constraintTop_toBottomOf="@+id/label"
  app:layout_constraintLeft_toLeftOf="@+id/label"
  android:text="@string/jasmine"
  android:textSize="24sp"/>

The width and the height of the ImageView is 0dp. This tells ConstraintLayout to compute them by the constraints. In this case, the height of the ImageView is determined by these constraints:

app:layout_constraintTop_toTopOf="@+id/label"
app:layout_constraintBottom_toBottomOf="@+id/text"

The width is the same as the height.

app:layout_constraintDimensionRatio="1:1"

The rest of the constraints are for positioning.

With that, the image scales up as the text sizes increase. It stays sharp because it is a vector.

Layout Editor

I tried to make this layout with the Layout Editor, but could not figure out how to create the constraint app:layout_constraintTop_toTopOf="@+id/label" for the ImageView. I was hovering my cursor around the top edge but not sure how to drag it to link the two views. So I added up playing with the editor a bit to deduce the XML attributes, and switched to editing the XML directly. I hope to use the layout editor in my next attempt to use Constraint Layout.

Follow-up Twitter discussion:

Read the whole Twitter thread.

Source code

github.com/chiuki/iconlabeltext

Click on either TextView to increase its size. Click on the image to reset.

Friday, March 18, 2016

ClipRect: Draw outside of the box

Do you know how to draw something like this in Android?

Yes, canvas.drawRoundRect()!

radius = height / 2;
rect.set(0, 0, width, height);
canvas.drawRoundRect(rect, radius, radius, paint);

How about something like this?

It kind of looks like the rectangle with rounded corners. If only there is a way to chop off the end! Well, there is: clipRect.

When you clip a canvas, you are telling Android to draw only inside that rectangle. This allows us to color outside of the box, so to speak, but only keep the part that is inside the box. In our case, we will draw a round rect with size width + height by height, but clip it to width by height so the rounded part on the right is "outside".

canvas.save();
canvas.clipRect(0, 0, width, height);

rect.set(0, 0, width + height, height);
canvas.drawRoundRect(rect, radius, radius, paint);

canvas.restore();

Remember to save and restore so the rest of your app can draw wherever it wants.

Challenge

The source code is in ClipRectActivity in android-graphics-demo.

Can you modify it to clip the rectangle on the other side?

Bonus: Try these other techniques!

Tuesday, October 6, 2015

Espresso: Save and restore state

Do you save and restore state of your activities, fragments and custom views? Do you test them?

One way to test saving and restoring state is to rotate the screen in your Espresso test.

private void rotateScreen() {
  Context context = InstrumentationRegistry.getTargetContext();
  int orientation 
    = context.getResources().getConfiguration().orientation;

  Activity activity = activityRule.getActivity();
  activity.setRequestedOrientation(
      (orientation == Configuration.ORIENTATION_PORTRAIT) ?
          ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE : 
          ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}

With this helper function, you can write tests like this:

@Test
public void incrementTwiceAndRotateScreen() {
  onView(withId(R.id.count))
      .check(matches(withText("0")));

  onView(withId(R.id.increment_button))
      .perform(click(), click());
  onView(withId(R.id.count))
      .check(matches(withText("2")));

  rotateScreen();

  onView(withId(R.id.count))
      .check(matches(withText("2")));
}

Source code

https://github.com/chiuki/espresso-samples/ under rotate-screen

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Monday, August 24, 2015

Friend Spell with Google Nearby API

Google recently rolled out Nearby API, allowing nearby devices to message to each other. When I saw that, I thought, party game!

Friend Spell

The goal of Friend Spell is to spell different words with the first letters of the names of your friends. In the example, Chris, Amy and Rae spell the word CAR. If you need to spell TAXI but Xavier isn't in the room, you can find any 4 people instead.

Download from Google Play:

play.google.com/store/apps/details?id=com.sqisland.friendspell

Open source

I have decided to open source the game so people can study an end-to-end app, complete with tests.

github.com/chiuki/friendspell

I did not check in my Nearby API and Google Plus credentials though, so you'll have to add your own com.google.android.nearby.messages.API_KEY in AndroidManifest.xml as well as app/google-services.json file. Refer to the Nearby API and Google Plus documentation for more information.

Technologies used in the project:

Droidcon NYC

Please download Friend Spell and try it out, especially if you are attending Droidcon NYC this week. I'd love to see it in action at a conference!

Tuesday, July 28, 2015

Espresso: Wait for dialog to dismiss

Did you complete the Espresso Idling Resource quiz? Here comes the solution!

DialogFragmentIdlingResource

In the quiz, the test fails because we were verifying the text before the loading dialog dismisses. To make it pass, we can add an idling resource that is only idle when the dialog is not shown.

public DialogFragmentIdlingResource(
    FragmentManager manager, String tag) {
  this.manager = manager;
  this.tag = tag;
}

@Override
public boolean isIdleNow() {
  boolean idle = (manager.findFragmentByTag(tag) == null);
  if (idle) {
    resourceCallback.onTransitionToIdle();
  }
  return idle;
}

The idea is to query the FragmentManager with the tag of the loading dialog. If it is null, the DialogFragment is not there, and we consider the app idle.

Using the idling resource in test

@Test
public void done() {
  IdlingResource idlingResource = new DialogFragmentIdlingResource(
      activityRule.getActivity().getSupportFragmentManager(),
      LoadingDialogFragment.TAG);

  Espresso.registerIdlingResources(idlingResource);

  onView(withId(R.id.text))
      .check(matches(withText(R.string.done)));

  Espresso.unregisterIdlingResources(idlingResource);
}

We create an DialogFragmentIdlingResource with the tag from the loading dialog so that it knows what to wait for. With that, the test will wait until the loading dialog dismisses before proceeding. And now the test passes!

Source code

https://github.com/chiuki/espresso-samples/ under idling-resource-dialog-fragment on the solution branch.

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Sunday, June 7, 2015

Espresso: Elapsed time

IdlingResource is a powerful concept in Espresso that is a little difficult to grasp. I have already written an example to wait for an IntentService to be idle, but I thought I'd write another example where it literally waits for time to pass.

Timer game

You start the timer, wait for a minute, and stop it. The game mocks you if you don't wait long enough:

If you wait for longer than a minute, the game congratulates you:

IdlingResource

To test this, we need to ask Espresso to wait. We could use SystemClock.sleep(60000), but that blocks the testing thread. Instead, we write a custom IdlingResource.

public ElapsedTimeIdlingResource(long waitingTime) {
  this.startTime = System.currentTimeMillis();
  this.waitingTime = waitingTime;
}

@Override
public boolean isIdleNow() {
  long elapsed = System.currentTimeMillis() - startTime;
  boolean idle = (elapsed >= waitingTime);
  if (idle) {
    resourceCallback.onTransitionToIdle();
  }
  return idle;
}

In the constructor, we save the current time, and also how long we need to wait. When Espresso wants to know if it should wait more, it calls isIdleNow(), which computes how much time has elapsed, and returns true if it is longer than the amount of time we need to wait.

Timeout policies

Besides the IdlingResource, we also need to change Espresso's timeout policies.

long waitingTime = DateUtils.SECOND_IN_MILLIS * 75;

IdlingPolicies.setMasterPolicyTimeout(
    waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(
    waitingTime * 2, TimeUnit.MILLISECONDS);

IdlingResource idlingResource 
    = new ElapsedTimeIdlingResource(waitingTime);
Espresso.registerIdlingResources(idlingResource);

By default, Espresso times out the app if it has been idle for 60 seconds, and times out an IdlingResource that has been idle for 26 seconds. We change the timeout policies to be twice our waiting time to make sure Espresso does not kill our test.

How does Espresso wait?

If you run this test, you will notice that the IdlingResource does not wait for the exact amount of time you specified, but slightly longer. Espresso waits by periodically checking if everything is idle, and your resource is only idle after the amount of time you specified, so it is guaranteed to wait for at least that long, but it could, and usually does, wait longer.

Full example

Check out the source code for a full example. Pay attention to see how the test cleans up by resetting the timeout policies and unregister the IdlingResource.

Source code: https://github.com/chiuki/espresso-samples/ under idling-resource-elapsed-time

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Monday, May 11, 2015

Non-breaking space

You just finished implementing the UI and sent the screenshot to your designer for review. This is the feedback: "Can we make sure we don't have a dangling last word? Like what I photoshopped in the second box?"

You scratched your head and thought, how do I do that? And then you remember your best friend from HTML: &nbsp;. Futhermore, you remember a post from a while ago:

&nbsp; stands for non-breaking space, and you can insert that into your Android XML by \u00A0 or &#160;. I prefer &#160; because Android Studio highlights it with blue:

This way Android will make sure the last two words are always on the same line.

Source code

https://github.com/chiuki/advanced-textview under NonBreakingSpaceActivity.

Friday, May 8, 2015

Espresso: Match Toolbar Title

How do check the value of the title bar in Espresso?

Let's take a look with Hierarchy Viewer. Remember to run it with ANDROID_HVPROTO=ddm if your device is not rooted.

First attempt: Match the TextView

We found our TextView in the hierarchy, but it does not have an id. However, we notice that it is a child of the Toolbar, so we can match it using withParent.

@Test
public void toolbarTitle() {
  CharSequence title = InstrumentationRegistry.getTargetContext()
    .getString(R.string.my_title);
  matchToolbarTitle(title);
}

private static ViewInteraction matchToolbarTitle(
    CharSequence title) {
  return onView(
      allOf(
          isAssignableFrom(TextView.class),
          withParent(isAssignableFrom(Toolbar.class))))
      .check(matches(withText(title.toString())));
}

This is how we look for the view to verify the title:

  • It is a TextView
  • It has a parent that is a Toolbar

This works, but it depends on the inner structure of Toolbar, which is not a part of the public API and may change in future versions. Let's make it better.

Second attempt: Custom matcher with Toolbar.getTitle()

private static ViewInteraction matchToolbarTitle(
    CharSequence title) {
  return onView(isAssignableFrom(Toolbar.class))
      .check(matches(withToolbarTitle(is(title))));
}

private static Matcher<Object> withToolbarTitle(
    final Matcher<CharSequence> textMatcher) {
  return new BoundedMatcher<Object, Toolbar>(Toolbar.class) {
    @Override public boolean matchesSafely(Toolbar toolbar) {
      return textMatcher.matches(toolbar.getTitle());
    }
    @Override public void describeTo(Description description) {
      description.appendText("with toolbar title: ");
      textMatcher.describeTo(description);
    }
  };
}

withToolbarTitle() returns a custom BoundedMatcher, which gives us type safety. In matchesSafely(), we call Toolbar.getTitle() to check its value.

To use this custom matcher, we change our helper function to look for the Toolbar itself, and check that it has the expected title. Notice that we pass is(title) to withToolbarTitle, which takes a text matcher rather than a string. That way we can use other matchers such as startsWith and endsWith.

Source code and notes

https://github.com/chiuki/espresso-samples/ under toolbar-title

  • The Toolbar is actually android.support.v7.widget.Toolbar because we are using AppCompat in the sample.
  • In both attempts, we assume that there is only one Toolbar. If you have more than one, you will need to have some extra matchers to pinpoint the one you want to match.
  • We use a helper function matchToolbarTitle() to hide the implementation details of how we match the toolbar title. That way, if the underlying code changes, we only need to update one place for all our tests that wants to match the toolbar title.
  • The helper function matchToolbarTitle() returns a ViewInteraction. This allows us to chain this call to verify other properties of the Toolbar. i.e. you can do something like this:
    matchToolbarTitle(title)
      .check(matches(isDisplayed()));
    

Article inspired by discussion with Danny Roa, Jacob Tabak and Jake Wharton.

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Saturday, April 25, 2015

Espresso: Custom Idling Resource

One key feature in Espresso is the synchronization between the test operations and the application being tested. This is built around the concept of idling: Espresso waits until the app is "idle" before performing the next action and checking the next assertion.

Idle

What does it mean for the app to be idle? Espresso checks for several conditions:

  • There is no UI events in the current message queue
  • There is no tasks in the default AsyncTask thread pool

This takes care of waiting for the UI rendering and AsyncTask completion. However, if your app performs long-running operations in other ways, Espresso will not know how to wait for those operations to finish. If that is the case, you can tell Espresso to wait by writing a custom IdlingResource.

IntentServiceIdlingResource

Say you use an IntentService to do some long computation, and return the result to your activity via broadcast. We want Espresso to wait until the result is returned before checking that it was displayed correctly.

To implement an IdlingResource, you need to override 3 functions: getName(), registerIdleTransitionCallback() and isIdleNow().

@Override
public String getName() {
  return IntentServiceIdlingResource.class.getName();
}

@Override
public void registerIdleTransitionCallback(
    ResourceCallback resourceCallback) {
  this.resourceCallback = resourceCallback;
}

@Override
public boolean isIdleNow() {
  boolean idle = !isIntentServiceRunning();
  if (idle && resourceCallback != null) {
    resourceCallback.onTransitionToIdle();
  }
  return idle;
}

private boolean isIntentServiceRunning() {
  ActivityManager manager = 
    (ActivityManager) context.getSystemService(
      Context.ACTIVITY_SERVICE);
  for (ActivityManager.RunningServiceInfo info : 
          manager.getRunningServices(Integer.MAX_VALUE)) {
    if (RepeatService.class.getName().equals(
          info.service.getClassName())) {
      return true;
    }
  }
  return false;
}

The idle logic is implemented in isIdleNow(). In our case, we check if our IntentService is running by querying the ActivityManager. If the IntentService is not running, we can inform Espresso calling resourceCallback.onTransitionToIdle().

Register your idling resource

You need to register your custom idling resource in order for Espresso to wait for it. Do it in a @Before method in your test, and unregister it in @After.

@Before
public void registerIntentServiceIdlingResource() {
  Instrumentation instrumentation 
    = InstrumentationRegistry.getInstrumentation();
  idlingResource = new IntentServiceIdlingResource(
    instrumentation.getTargetContext());
  Espresso.registerIdlingResources(idlingResource);
}

@After
public void unregisterIntentServiceIdlingResource() {
  Espresso.unregisterIdlingResources(idlingResource);
}

Full example

Check out the source code for a full example. Try commenting out the IdlingResource registration and watch the test fail.

Source code: https://github.com/chiuki/espresso-samples/ under idling-resource-intent-service

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Tuesday, April 21, 2015

Espresso 2.1: ActivityTestRule

When I wrote my android-test-demo app, I wanted to use Jake Wharton's ActivityRule, but couldn't because I wanted to customize the launch intent for each test method.

Today I got my solution from Google: Test rules in Espresso 2.1. ActivityTestRule can be configured to take a different launch intent per test method like this:

@Rule
public ActivityTestRule activityRule = new ActivityTestRule<>(
    MainActivity.class,
    true,    // initialTouchMode
    false);  // launchActivity. False to set intent per method

I then supply a launch intent in the test method:

@Test
public void intent() {
  DateTime dateTime = new DateTime(2014, 10, 15, 0, 0, 0);
  Intent intent = new Intent();
  intent.putExtra(MainActivity.KEY_MILLIS, dateTime.getMillis());

  activityRule.launchActivity(intent);

  onView(withId(R.id.date))
      .check(matches(withText("2014-10-15")));
}

See MainActivityTest.java for more details.

ActivityTestRule makes tests much cleaner. Thank you Google for making Espresso better and better!

Like this article? Take a look at the outline of my Espresso book and fill in this form to push me to write it! Also check out the published courses: https://gumroad.com/chiuki

Thursday, April 9, 2015

Dagger 2 + Espresso 2 + Mockito

I've been doing Android instrumentation testing with Dagger, Espresso and Mockito, and I love it. To commemorate the launch of Dagger 2 out of SNAPSHOT, I am sharing a demo repo with Dagger 2, Espresso 2 and Mockito:

https://github.com/chiuki/android-test-demo

Dagger Components

Dependency injection allows our app and test obtain different modules. This is very useful for creating repeatable test cases. The demo app displays today's date in the format yyyy-MM-dd. We would like to test that against a known date, instead of depend on the actual date when we run the test.

In Dagger 2, a Component provides modules for your whole app, and defines where to inject them.

public interface DemoComponent {
  void inject(MainActivity mainActivity);
}
@Singleton
@Component(modules = ClockModule.class)
public interface ApplicationComponent extends DemoComponent {
}
@Singleton
@Component(modules = MockClockModule.class)
public interface TestComponent extends DemoComponent {
  void inject(MainActivityTest mainActivityTest);
}

ApplicationComponent is used when the app is run normally, and TestComponent is used during tests. Both components injects into MainActivity.

How does the MainActivity know which component to use? It injects via the application, which stores the component.

Approach 1: setComponent

private DemoComponent component = null;

@Override public void onCreate() {
  super.onCreate();
  if (component == null) {
    component = DaggerDemoApplication_ApplicationComponent
        .builder()
        .clockModule(new ClockModule())
        .build();
  }
}

public void setComponent(DemoComponent component) {
  this.component = component;
}

public DemoComponent component() {
  return component;
}

We call setComponent() in test, which runs before onCreate(), so the TestComponent is used. When the app is run normally, component will be set to ApplicationComponent in onCreate().

Approach 2: Mock application

Exposing setComponent in the application is not great because ideally the application should not have test-specific code. Another approach is to subclass the application in the androidTest folder and load it during tests via a custom test runner. See blog.sqisland.com/2015/12/mock-application-in-espresso.html for more details.

Mockito

The app has a Clock class which returns the current time.

public DateTime getNow() {
  return new DateTime();
}

TestComponent contains MockClockModule, which provides Clock as mocked by Mockito. This way MainActivityTest can supply a pre-determined date during test.

Mockito.when(clock.getNow())
  .thenReturn(new DateTime(2008, 9, 23, 0, 0, 0));

Since we have singleton modules, the same mocked Clock is supplied to the app. With that, it will display the provided date instead of today's date:

onView(withId(R.id.date))
  .check(matches(withText("2008-09-23")));

More

There is a lot more in the repo, including testing activity launch with intent and unit testing with JUnit. Please check it out:

https://github.com/chiuki/android-test-demo

Look at the setComponent branch to see the old code for injection using the setComponent function. Master uses mock application and custom test runner for injection.

Also, I am toying with the idea of writing a book on Espresso. Please take a look at the outline and fill in this form if you think I should write it!

Update: I have started publishing Espresso courses! https://gumroad.com/chiuki

Further reading:

Wednesday, September 19, 2012

Android: Swipe Image Viewer with ViewPager

A few months ago I complained that there is no standard widget for a swipe image viewer. I just asked about it on Twitter, and discovered that ViewPager is what I wanted.

I updated my github project with a ViewPager implementation, and it's so much simpler. On top of that, the images slide as your finger swipes across the screen, giving a much more satisfactory feedback.

Sunday, August 26, 2012

Browser extensions workshop

CodeChix held a Browser Extensions workshop, led by Sharon Minsuk. She described what browser extensions are, showed us a few demos, and then we had some hands-on coding exercises.

I toyed with browser extensions briefly, but stopped because I couldn't figure out how to include jquery. After the workshop today I got inspired again, and wrote my first extension to add tooltips for airport codes.

Say you are reading some forum post with lots of airport codes:

My extension will add an airplane icon after each 3-letter airport code. Hover over the airplane gives you the name and location of the airport:

The code is on github: https://github.com/chiuki/airport-tooltip-extension. You can also download the packaged extension directly.

The parsing is pretty crude, and I didn't use any jquery at the end.

It only took me an hour or so to make the extension, and I'm quite happy with the results. Thank you Sharon for the excellent workshop!

Monday, July 30, 2012

Android: Swipe Image Viewer

EDIT: I have a much simpler implementation with ViewPager, and the github project has been updated. See post here.


I have a GrideView of images, which enlarges to full screen when you tap on any image. When I showed it to my friend he swiped to get to the next image, but that did not do anything because I was just using a plain old ImageView. I looked around for a standard widget to do that, and couldn't find any. So I unwillingly rolled my own solution: http://github.com/chiuki/android-swipe-image-viewer.

I show the image in an ImageSwitcher. It contains two views, one displays the current image, and the other one holds the image to swap in. I lifted the gesture listener code from the Android Gallery app:

private class SwipeListener extends SimpleOnGestureListener {
  private static final int SWIPE_MIN_DISTANCE = 75;
  private static final int SWIPE_MAX_OFF_PATH = 250;
  private static final int SWIPE_THRESHOLD_VELOCITY = 200;

  @Override
  public boolean onFling(MotionEvent e1, MotionEvent e2, 
      float velocityX, float velocityY) {
    try {
      if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
        return false;
      // right to left swipe
      if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE
          && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
        moveNextOrPrevious(1);
      } else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE
          && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
        moveNextOrPrevious(-1);
      }
    } catch (Exception e) {
      // nothing
    }
    return false;
  }
}

moveNextOrPrevious() swaps in the previous or next image with the appropriate animation:

private void moveNextOrPrevious(int delta) {
  int nextImagePos = mCurrentPosition + delta;
  if (nextImagePos < 0) {
    mOverscrollLeft.setVisibility(View.VISIBLE);
    mOverscrollLeft.startAnimation(mOverscrollLeftFadeOut);
    return;
  }
  if (nextImagePos >= mImages.length) {
    mOverscrollRight.setVisibility(View.VISIBLE);
    mOverscrollRight.startAnimation(mOverscrollRightFadeOut);
    return;
  }

  mImageSwitcher.setInAnimation(
    delta > 0 ? mSlideInRight : mSlideInLeft);
  mImageSwitcher.setOutAnimation(
    delta > 0 ? mSlideOutLeft : mSlideOutRight);

  mCurrentPosition = nextImagePos;
  mImageSwitcher.setImageResource(mImages[mCurrentPosition]);
}

Notice how I show an overscroll grow when the user reaches the start or the end of the list of images. I make it visible immediately, and then use an animation to fade it away.

Right now all the logic is in MainActivity. I thought about encapsulating the viewer code into a custom view and bundling it into a jar file, but I am not sure how many people will find it useful, so laziness got the better of me. If you want to get a jar file to reuse this code, please let me know.

Until then, here is the source code: http://github.com/chiuki/android-swipe-image-viewer.

Monday, January 16, 2012

Android: Pendulum Animation

I am working on the splash screen for Monkey Write, and I would like the monkey to be swinging on a vine. In Android you can specify all kinds of animations with xml, so it took me a little bit of time to figure out all the perimeters. Here is my final swinging.xml:

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
  android:fromDegrees="15"
  android:toDegrees="-15"
  android:pivotX="50%"
  android:pivotY="0%"
  android:duration="2000"
  android:repeatMode="reverse"
  android:repeatCount="-1"
/>

I am using a pivoted rotation to implement the swinging effect. The animation goes from 15 degrees to -15 degrees, but instead of pivoting at the center, I want the pivot point to be at the top-middle. This is achieved by pivotX="50%" and pivotY="0%". For non-stop animation, I use repeatCount="-1".

It was fairly straightforward figure out all those parameters. But the monkey swings from the left to right, then jumps back to the left before swinging again. To make him swing back and forth, I need to set the repeatMode to reverse. Now I have a properly swinging monkey!

I have uploaded my code here: http://github.com/chiuki/android-pendulum-animation. Instead of a monkey, I use xml to specify a pendulum, which seems appropriate for the animation.

Monday, January 9, 2012

Android: Square View

In Monkey Write, the character sits in a square. Initially I just hard-coded the width to be equal to the height, but I wanted them to scale with the device dimensions. Today I found an elegant way to do it, with 3 lines of code in my custom View:

public void onMeasure(int widthSpec, int heightSpec) {
  super.onMeasure(widthSpec, heightSpec);
  int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
  setMeasuredDimension(size, size);
}

Since my class extends View, I can reuse all the measurement logic by calling super. After that, I take the measured width and height, find the smaller one, and use it to set the width to be the same as the height.

I made a sample project to test the SquareView. It is set to take one third of the screen width, and the easiest way to try it with different dimensions is to rotate the screen.

Source code: http://github.com/chiuki/android-square-view.

Friday, November 18, 2011

Android: Protect ContentProvider with signature

I have been looking for secure ways for my Android apps to share data, and was looking into permissions. In particular, the signature level in android:protectionLevel looks really promising:

A permission that the system grants only if the requesting application is signed with the same certificate as the application that declared the permission. If the certificates match, the system automatically grants the permission without notifying the user or asking for the user's explicit approval.

The idea is to have a main app for displaying data, and multiple modules supplying the data. Permissions only work with one of the four application components: Activity, Service, ContentProvider, and BroadcastReceiver. ContentProvider is the logical choice for serving data.

Loading sqlite database from resource

It is a bit of a pain to write a ContentProvider, though. Since all I wanted was to test the permissions, I took a shortcut: I followed this tutorial and loaded a sqlite database from resource. To automate the database creation, I wrote a script to load the data from a tab-delimited file.

Permission setup

It took me many tries to get the permissions right. I know that the <provider> need to request the permission, but where do I declare it? I tried both in the module and the main app, but no luck, the main app could use the ContentProvider no matter how I signed the apps. Turns out it was a stupid error: I declared the permission with the <provider>, inside of the <application> block. It should go outside!

So the formula looks like this:

  • Declare the permission in the provider app
  • Request the permission in the provider app
  • Use the permission in the consumer app

When everything was setup properly, it was very satisfying to see my app throwing java.lang.SecurityException when I sign it with a non-matching certificate.

The code

I uploaded some sample code to github: http://github.com/chiuki/android-protected-provider. Hope you found it useful!