The first second of an app's launch

Anas Ambri

Android Montreal group - September 2016

$whoami

Android & iOS freelancer

@AnasAmbri

Inspiration

Taken from [1]

Question of the day

Can we create a timeline for what happens when we click on app icon?

Motivation

Taken from [2]

Just to make it clear

Will this be useful for you tomorrow?

Probably not

Will it help you make better decisions ?

Definitely

More practical reasons

Outline

  1. How the OS detects a touch
  2. How an Android app receives a touch
  3. How the launcher screen works
  4. How the selected app is launched

If you have a question, at any point, stop me.

Tools

Part 1: How your phone can feel your touch

At the lowest* level

Driver converts touches into a stream of events

 

Exposes them in a file (/dev/input/eventX)

  • /dev/input/event0 is the screen
  • /dev/input/event1 is the home button

getevent -lt

getevent gives a live dump of input events

If you have a rooted device:

adb shell su getevent -lt

(sendevent can actually send events too)

Live dump

Live dump (2)

Live dump (3)

EventHub

A class that buffers all events

Full implementation

EventHub (2)

1  size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
    //...
3     RawEvent* event = buffer;
4     while (mPendingEventIndex < mPendingEventCount) {
5         Device* device = mDevices.valueAt(deviceIndex);
        //...
7         if (eventItem.events & EPOLLIN) {
8             int32_t readSize = read(device->fd, readBuffer, 
                sizeof(struct input_event) * capacity);
            //...
10            size_t count = size_t(readSize) / sizeof(struct input_event);
11            for (size_t i = 0; i < count; i++) {
12                struct input_event& iev = readBuffer[i];
13                    event->deviceId = deviceId;
14                    event->type = iev.type;
15                    event->code = iev.code;
16                    event->value = iev.value;
17                    event += 1;
18                }
19            }
20        }
21    }
22 }

InputReader

A class that notifies upper layers of change

Full implementation

InputReader (2)

1  void InputReader::loopOnce() {
2      Vector<InputDeviceInfo> inputDevices;
3      size_t count = mEventHub->getEvents(
           timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

5      { //...
6          if (count) {
7              processEventsLocked(mEventBuffer, count);
8      }
       //...

10     // Send out a message that the describes the changed input devices.
11     if (inputDevicesChanged) {
12         mPolicy->notifyInputDevicesChanged(inputDevices);
13     }
14 }

InputManagerService

This is a JNI class (C++ & Java implementations)

GET_METHOD_ID(gServiceClassInfo.notifyInputDevicesChanged, clazz,
    "notifyInputDevicesChanged", "([Landroid/view/InputDevice;)V");

private void notifyInputDevicesChanged(InputDevice[] inputDevices) {
    //...
    mHandler.obtainMessage(MSG_DELIVER_INPUT_DEVICES_CHANGED,
                    mInputDevices).sendToTarget();
}

private final class InputManagerHandler extends Handler {
    //...
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_DELIVER_INPUT_DEVICES_CHANGED:
                deliverInputDevicesChanged((InputDevice[])msg.obj);
                break;
        }
        //...
    }
}

WindowManagerService

  • A system service
  • Takes care of communication between apps and anything that has to do with the Window
  • Full implementation

Part 2: How an app receives a touch

A Familiar face: View

A view has a ViewRootImpl

A ViewRootImpl has a WindowInputEventReceiver

WindowInputEventReceiver extends InputEventReceiver

 InputEventReceiver has two methods 

- handleEvent()

- consumeEvents()

WindowManagerService keeps an eye on all InputEventReceivers

InputEventReceiver

1 int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
     //...
2    JNIEnv* env = AndroidRuntime::getJNIEnv();
3    status_t status = consumeEvents(env, false /*consumeBatches*/, -1);
4 }

6 status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
7     bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {

9     for (;;) {
10        InputEvent* inputEvent;
11        status_t status = mInputConsumer.consume(&mInputEventFactory,
              consumeBatches, frameTime, &seq, &inputEvent);

          //...
13        inputEventObj = android_view_KeyEvent_fromNative(env,
            static_cast<KeyEvent*>(inputEvent));
          //...
15        env->CallVoidMethod(receiverObj.get(),
             gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
16    }
17 }

Also JNI code (C++ & Java parts)

Back to the View

1  public abstract class InputEventReceiver {
2      private void dispatchInputEvent(int seq, InputEvent event) {
3          mSeqMap.put(event.getSequenceNumber(), seq);
4          onInputEvent(event);
5      }
6  }

7  final class WindowInputEventReceiver extends InputEventReceiver {
8      public void onInputEvent(InputEvent event) {
9          enqueueInputEvent(event, this, 0, true);
10     }
11 }

12 public final class ViewRootImpl {
13     void enqueueInputEvent(InputEvent event,
14         InputEventReceiver receiver, int flags, boolean processImmediately) {
           //...
16         if (processImmediately) {
17             doProcessInputEvents();
18         } else {
19             scheduleProcessInputEvents();
20         }
21     }
22 }

The View, at last

private int processKeyEvent(QueuedInputEvent q) {
    final KeyEvent event = (KeyEvent)q.mEvent;

    // Deliver the key to the view hierarchy.
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }
}

The View, at last

Summary of first two parts

Bottom-up

Drivers send events

Events are buffered, then forwarded to the WindowManagerService

Top-down

Each view has an InputEventReceiver

InputEventReceiver receive events from the WindowManagerService

Part 3: How the launcher works

Pretty easy, actually

A launcher is just an app

Demo

<!-- This is the home screen -->
<activity
    android:name=".activity.HomeActivity" android:label="Fake Launcher"
    android:theme="@android:style/Theme.Wallpaper.NoTitleBar"
    android:launchMode="singleTask" android:stateNotNeeded="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.HOME" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

<!-- This is needed, or else your app won't compile -->
<activity
    android:name=".activity.LaunchActivity" 
    android:theme="@android:style/Theme.NoDisplay">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Pretty easy (2)

1  //Retrieve all apps on the phone
2  Intent i = new Intent(Intent.ACTION_MAIN, null);
3  i.addCategory(Intent.CATEGORY_LAUNCHER);

5  List<ResolveInfo> availableActivities = packageManager.queryIntentActivities(i, 0);
6  for(ResolveInfo ri:availableActivities){
7  AppDetails details = new AppDetails(
8      ri.activityInfo.loadLabel(packageManager).toString(),
9      ri.activityInfo.packageName,
10     ri.activityInfo.loadIcon(packageManager)
11 );
12 }

14 //Start App
15 Intent intent = context.getPackageManager()
    .getLaunchIntentForPackage(appDetails.getPackageName());
16 if (intent != null) {
17     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
18     context.startActivity(intent);
19 }

Part 4: How the selected app is launched

ActivityManagerService

Like WindowManagerService, but for activities

ResolveInfo rInfo = ActivityThread.getPackageManager().resolveIntent(
            intent, resolvedType, PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS);

intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));

This will submit a request to start the Activity (after checking permissions)

Process

Eventually, we create a process

// Is this activity's application already running?
1  ProcessRecord app = getProcessRecordLocked(r.processName, r.info.appInfo.uid);

2  if (app != null && app.thread != null) {
3      try {
4          realStartActivityLocked(r, app, andResume, checkConfig);
5          return;
6      } catch (RemoteException e) { }
7  }

9  startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
          "activity", r.intent.getComponent(), false);


10 int pid = Process.start("android.app.ActivityThread",
         mSimpleProcessManagement ? app.processName : null, uid, uid,
         gids, debugFlags, null);

Process (2)

Eventually, we create a process

1  try {
       return startViaZygote(processClass, niceName, uid, gid, gids,
              debugFlags, zygoteArgs);
2  } catch (ZygoteStartFailedEx ex) {
3      throw new RuntimeException(
            "Starting VM process through Zygote failed", ex);
4  }

5  private static ProcessStartResult startViaZygote(final String processClass,
                                  //...) throws ZygoteStartFailedEx {
6  return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}

Zygote

This is a process that gets copied for every app

pid = Zygote.fork();

A process is a wrapper around a task

By forking, the OS saves resources

Remember this?

Summary

Resources

  • androidxref http://androidxref.com/7.0.0_r1/
  • getevent https://source.android.com/devices/input/getevent.html
  • sendevent http://ktnr74.blogspot.ca/2013/06/emulating-touchscreen-interaction-with.html
  • System services https://anatomyofandroid.com/2013/10/03/system-services/
  • Input handling https://seasonofcode.com/posts/internal-input-event-handling-in-the-linux-kernel-and-the-android-userspace.html

References

[1]: http://archive.ncsa.illinois.edu/Cyberia/Cosmos/CosmicMysteryTour.html

[2]: http://www2.r3gis.fr/CSSPresentation/#/8

Thank you!

Would love to hear your feedback @AnasAmbri

Made with Slides.com