Task & Document API

Task & Document API

Tasks are the plumbing of Android apps: you use them every day when developing. But the Task API in Android is one of the most difficult API to apprehend. The behaviour fragmentation between the Android versions does not help us either: taskAffinity, launchMode, flags, etc. In addition to the Task API, Android 5.0 introduced the Document Task API. This new feature is a big upheaval on how we manage tasks as Android developers and users.

In this presentation, we will have an in depth look at these APIs. We will see how to use them in typical use cases.

Video available here: https://www.youtube.com/watch?v=NTWmxp8dzBs

9949dfe0542bd0fd32676d63c97a625f?s=128

Mathieu Calba

November 10, 2015
Tweet

Transcript

  1. Task & Document API Droidcon Paris 2015

  2. @Mathieu_Calba +MathieuCalba

  3. @Mathieu_Calba +MathieuCalba

  4. What is a Task?

  5. What is a Task? An Activity represents a screen

  6. What is a Task? An application is often composed of

    many activities
  7. What is a Task? An application is often composed of

    many activities
  8. What is a Task? An application is often composed of

    A task is an ordered queue of activities called “back stack”
  9. What is a Task? Tasks are presented in the overview

    screen
  10. What is a Task? • Since 1.0: icon and name

    • Screenshot added in 4.3 • Icon and name customisation added in 5.0 Overview screen
  11. What is a Task? Overview screen

  12. How does it work?

  13. How does it work? 2 main ways: • Attributes on

    the <activity> tag in the AndroidManifest.xml • Flags on the Intent Task API
  14. How does it work? android:allowTaskReparenting android:alwaysRetainTaskState android:autoRemoveFromRecents android:clearTaskOnLaunch android:documentLaunchMode android:excludeFromRecents

    android:finishOnTaskLaunch Task API android:launchMode android:maxRecents android:noHistory android:relinquishTaskIdentity android:stateNotNeeded android:taskAffinity
  15. How does it work? Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT Intent.FLAG_ACTIVITY_CLEAR_TASK Intent.FLAG_ACTIVITY_CLEAR_TOP Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS Intent.FLAG_ACTIVITY_FORWARD_RESULT

    Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY Intent.FLAG_ACTIVITY_MULTIPLE_TASK Intent.FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_TASK Task API Intent.FLAG_ACTIVITY_NO_ANIMATION Intent.FLAG_ACTIVITY_NO_HISTORY Intent.FLAG_ACTIVITY_NO_USER_ACTION Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Intent.FLAG_ACTIVITY_REORDER_TO_FRONT Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS Intent.FLAG_ACTIVITY_SINGLE_TOP Intent.FLAG_ACTIVITY_TASK_ON_HOME Intent.FLAG_ACTIVITY_HELL
  16. Have fun with flags!

  17. Standard behaviour

  18. Standard behaviour Action: android.intent.action.MAIN Category: android.intent.category.LAUNCHER Flags: • Intent.FLAG_ACTIVITY_NEW_TASK •

    Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Start from launcher
  19. Standard behaviour Start from launcher Action: android.intent.action.MAIN Category: android.intent.category.LAUNCHER Flags:

    • Intent.FLAG_ACTIVITY_NEW_TASK • Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  20. Action: android.intent.action.MAIN Category: android.intent.category.LAUNCHER Flags: • Intent.FLAG_ACTIVITY_NEW_TASK • Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Standard

    behaviour Start from launcher My awesome app
  21. My awesome app Standard behaviour Start from launcher Action: android.intent.action.MAIN

    Category: android.intent.category.LAUNCHER Flags: • Intent.FLAG_ACTIVITY_NEW_TASK • Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  22. My awesome app Standard behaviour Start from launcher Action: android.intent.action.MAIN

    Category: android.intent.category.LAUNCHER Flags: • Intent.FLAG_ACTIVITY_NEW_TASK • Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  23. Intent.FLAG_ACTIVITY_NEW_TASK Start the activity in a new Task depending on

    the task’s affinity Standard behaviour Start from launcher
  24. android:taskAffinity Used to determine in which task this activity will

    be launched when started with Intent.FLAG_ACTIVITY_NEW_TASK Application’s package name by default Standard behaviour Start from launcher
  25. Action: null Category: null Flags: null My awesome app Standard

    behaviour Start from within the app My awesome screen
  26. My awesome app My awesome screen Standard behaviour Start from

    within the app Action: null Category: null Flags: null
  27. Standard behaviour Start from overview My awesome app My awesome

    screen Action: null Category: null Flags: null
  28. My awesome app My awesome screen Standard behaviour Start from

    overview Action: null Category: null Flags: null
  29. Standard behaviour Start from overview My awesome app My awesome

    screen Action: null Category: null Flags: null
  30. Standard behaviour Start from overview My awesome app My awesome

    screen Action: null Category: null Flags: null
  31. My awesome app Same Intent that launched this activity Action:

    android.intent.action.MAIN Category: android.intent.category.LAUNCHER Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Standard behaviour Restart from back My awesome app My awesome screen
  32. My awesome app Same Intent that launched this activity Action:

    android.intent.action.MAIN Category: android.intent.category.LAUNCHER Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Standard behaviour Restart from back My awesome app My awesome screen
  33. But there is more to it

  34. But there is more to it USE ANOTHER APP TO

    DO A JOB MANY ENTRY POINTS INTO OUR APP MULTITASKING INSIDE AN APP
  35. But there is more to it USE ANOTHER APP TO

    DO A JOB MANY ENTRY POINTS INTO OUR APP MULTITASKING INSIDE AN APP
  36. But there is more to it USE ANOTHER APP TO

    DO A JOB MANY ENTRY POINTS INTO OUR APP MULTITASKING INSIDE AN APP
  37. Using other applications startActivity(new Intent().setAction(Intent.ACTION_VIEW) .setData(Uri.parse("http://captaintrain.com"));
 What behaviour does occur?

    New task or not? Opening an Url
  38. Using other applications Opening an Url It depends on the

    opened application! startActivity(new Intent().setAction(Intent.ACTION_VIEW) .setData(Uri.parse("http://captaintrain.com"));
 What behaviour does occur? New task or not?
  39. Using other applications • With the old Android browser: new

    task • With Firefox: new task • With Chrome: same task Opening an Url
  40. Using other applications Same task Action: android.intent.action.VIEW Category: null Flags:

    Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Opening an Url
  41. Using other applications Opening an Url New task Action: android.intent.action.VIEW

    Category: null Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
  42. Using other applications Opening an Url New task Action: android.intent.action.VIEW

    Category: null Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP WHY DID IT APPEAR?
  43. Using other applications In the old Android browser’s manifest: !

    <activity
 android:label="@string/app_name"
 android:launchMode="singleTask"
 android:name=".MainActivity"
 android:theme="@style/AppTheme.NoActionBar"> Opening an Url
  44. Using other applications In the old Android browser’s manifest: !

    <activity
 android:label="@string/app_name"
 android:launchMode="singleTask"
 android:name=".MainActivity"
 android:theme="@style/AppTheme.NoActionBar"> Opening an Url
  45. android:launchMode Determines how an activity is launched

  46. android:launchMode • New instance of the activity all the time

    • Can be anywhere in the stack • Which task is determined by the presence of FLAG_ACTIVITY_NEW_TASK and the android:taskAffinity attribute android:launchMode=“standard”
  47. android:launchMode • New instance of the activity most of the

    time • Can be anywhere in the stack • Which task is determined by the presence of FLAG_ACTIVITY_NEW_TASK and the android:taskAffinity attribute • If the targeted task already has this activity at the top of it with this android:launchMode, instead of creating a new instance, it just send a new Intent to this instance, which will receive it in the onNewIntent() method android:launchMode=“singleTop”
  48. android:launchMode • Only one instance of the activity • Always

    the root activity of the task • Other activities can be in its task (only standard and singleTop activities) android:launchMode=“singleTask”
  49. android:launchMode • Only one instance of the activity • Always

    the root activity of the task • Alone in its task • Every startActivity() behave just like if the Intent has the FLAG_ACTIVITY_NEW_TASK flag android:launchMode=“singleInstance”
  50. android:launchMode android:launchMode behaviour can be overridden by the FLAG_ACTIVITY_* in

    the intent
  51. Using other applications What is the correct behaviour? This way

    (app -> browser), it’s better to have them in the same task, because the browser is a utility Nothing can be done to prevent a new task to be launched here Opening an Url
  52. Using other applications Taking a picture final Intent intent =

    new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 startActivityForResult(intent, REQUEST_CODE_IMAGE_CAPTURE);
  53. Using other applications Taking a picture Only works when activity

    started in the same task Otherwise, an Activity.RESULT_CANCELED is received directly final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 startActivityForResult(intent, REQUEST_CODE_IMAGE_CAPTURE);
  54. Using other applications Sharing via email But it launch in

    the same task, which is not the expected behaviour on 5.0+ final Intent target = new Intent()
 .setAction(Intent.ACTION_SENDTO)
 .setData(Uri.fromParts(“mailto", "email@address.com", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 startActivity(Intent.createChooser(target, "My chooser"));
  55. Using other applications Sharing via email Action: android.intent.action.SENDTO Category: null

    Data: mailto:email@address.com Flags: Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP final Intent target = new Intent()
 .setAction(Intent.ACTION_SENDTO)
 .setData(Uri.fromParts(“mailto", "email@address.com", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 startActivity(Intent.createChooser(target, "My chooser"));
  56. Using other applications Sharing via email Action: android.intent.action.SENDTO Category: null

    Data: mailto:email@address.com Flags: Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP final Intent target = new Intent()
 .setAction(Intent.ACTION_SENDTO)
 .setData(Uri.fromParts(“mailto", "email@address.com", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 startActivity(Intent.createChooser(target, "My chooser"));
  57. Using other applications Intent.FLAG_ACTIVITY_NEW_TASK? No, because we don’t want to

    be launched in the targeted application’s task Sharing via email
  58. Using other applications Intent.FLAG_ACTIVITY_NEW_DOCUMENT (5.0+) • Start a new task

    dedicated to this job • New mail and original application are accessible via the overview screen • Restarting the original task from overview will not bring the mail activity Sharing via email
  59. Using other applications Compatibility: Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET • Start a the job

    in the same task • Restarting the original task from overview will not bring the mail activity as it will be finished Sharing via email
  60. Using other applications final Intent target = new Intent()
 .setAction(Intent.ACTION_SENDTO)


    .setData(Uri.fromParts(“mailto", "email@address.com", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 target.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
 } else {
 target.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
 }
 startActivity(Intent.createChooser(target, "My chooser")); Sharing via email
  61. Using other applications Sharing via email final Intent target =

    new Intent()
 .setAction(Intent.ACTION_SENDTO)
 .setData(Uri.fromParts(“mailto", "email@address.com", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 target.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
 } else {
 target.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
 }
 startActivity(Intent.createChooser(target, "My chooser"));
  62. Multiple entry points Internally: • From the notification • From

    a widget • From a wear app Externally: • Multiple icons in the launcher • Responding to a public Intent (URL, sharing, etc)
  63. Multiple entry points Internally

  64. Multiple entry points Internally final PendingIntent pendingIntent = TaskStackBuilder.create(this)
 .addNextIntentWithParentStack(DetailsActivity.

    newFocusOnBarcodeIntent(this, id)) .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); Action: null Category: null Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
  65. Multiple entry points Allow us to create a stack of

    activities Internally final PendingIntent pendingIntent = TaskStackBuilder.create(this)
 .addNextIntentWithParentStack(DetailsActivity. newFocusOnBarcodeIntent(this, id)) .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
  66. Multiple entry points TaskStackBuilder.create(this)
 .addParentStack(ParentActivity.class)
 .addNextIntent(detailIntent)
 .startActivities(); Internally Add the

    parent’s stack as defined by android:parentActivityName in AndroidManifest.xml
  67. Multiple entry points Add the targeted activity’s Intent Internally TaskStackBuilder.create(this)


    .addParentStack(ParentActivity.class)
 .addNextIntent(detailIntent)
 .startActivities();
  68. Multiple entry points Internally Do both at the same time

    TaskStackBuilder.create(this)
 .addNextIntentWithParentTask(detailIntent)
 .startActivities();
  69. Multiple entry points Internally Start the stack TaskStackBuilder.create(this)
 .addNextIntentWithParentTask(detailIntent)
 .startActivities();

  70. Multiple entry points Internally Create a pending Intent to be

    launched TaskStackBuilder.create(this)
 .addNextIntentWithParentTask(detailIntent)
 .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
  71. Multiple entry points Multiple icons in the app drawer Use

    cases very uncommon: app with multiple specific jobs Multiple activities declared with intent-filter: ! <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter> Become interesting with non-standard android:launchMode Externally
  72. Multiple entry points Intercepting Urls: ! <intent-filter>
 <action android:name="android.intent.action.VIEW" />


    
 <category android:name="android.intent.category.DEFAULT" />
 <category android:name="android.intent.category.BROWSABLE" />
 
 <data android:scheme="http" />
 <data android:scheme="https" />
 <data android:host="captaintrain.com" />
 <data android:host="www.captaintrain.com" />
 <data android:path="/search" />
 <data android:pathPrefix="/search/" />
 </intent-filter> Externally
  73. Multiple entry points From the old Android browser: Action: android.intent.action.VIEW

    Category: android.intent.category.BROWSABLE Data: https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Externally S AM E TASK
  74. Multiple entry points From the email app: Action: android.intent.action.VIEW Category:

    android.intent.category.BROWSABLE Data: https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET Intent.FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NO_ANIMATION Externally N EW TASK
  75. Multiple entry points From the email app: Action: android.intent.action.VIEW Category:

    android.intent.category.BROWSABLE Data: https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET Intent.FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NO_ANIMATION Externally N EW TASK
  76. Multiple entry points From Chrome: Action: android.intent.action.VIEW Category: android.intent.category.BROWSABLE Data:

    https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Externally N EW TASK
  77. Multiple entry points From Chrome: Action: android.intent.action.VIEW Category: android.intent.category.BROWSABLE Data:

    https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Externally N EW TASK
  78. Multiple entry points What is the good behaviour? Externally

  79. Multiple entry points What is the good behaviour? Externally !

    New task!
  80. Multiple entry points 2 solutions: • let the current behaviour

    and let some user have some bad experience • force the creation of a new task for better user experience at the expense of clean code Externally
  81. Multiple entry points 2 solutions: • let the current behaviour

    and let some user have some bad experience • force the creation of a new task for better user experience at the expense of clean code Externally Here come android:launchMode=“singleTask” to the rescue!
  82. Multiple entry points Create a special activity that will catch

    all Urls you need to catch: ! <activity
 android:launchMode="singleTask"
 android:name=".UrlCatcherActivity"
 android:theme="@android:style/Theme.NoDisplay">
 <intent-filter> <action android:name="android.intent.action.VIEW" />
 
 <category android:name="android.intent.category.DEFAULT" />
 <category android:name="android.intent.category.BROWSABLE" />
 
 <data android:scheme="http" />
 <data android:scheme="https" />
 <data android:host="mydomain.com" />
 </intent-filter>
 </activity> And then it will redirect to the correct activity and finish itself Externally
  83. Multitasking inside an app Why? • multi-document opened at the

    same time • writing an email and looking at other emails as references • multi-tasking at the system level is quicker than at the app level
  84. Introducing Document API (5.0+)

  85. Introducing Document API (5.0+) Move from app-centric to document-centric system

  86. Introducing Document API (5.0+) An extension of the Tasks API

    Move from app-centric to document-centric system
  87. Multitasking inside an app Only one instance of this document,

    one task Same as setting FLAG_ACTIVITY_NEW_DOCUMENT alone on the Intent android:launchMode=“standard” mandatory Create new document <activity
 android:label="NewDocument"
 android:documentLaunchMode="intoExisting"
 android:name=".NewDocumentActivity" />
  88. Multitasking inside an app Multiple instance of this document, multiple

    tasks Same as setting FLAG_ACTIVITY_NEW_DOCUMENT and FLAG_ACTIVITY_MULTIPLE_TASK on the Intent android:launchMode must be standard Create new document <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:name=".NewDocumentActivity" />
  89. Multitasking inside an app Create new document Default value of

    the attribute New task only if FLAG_ACTIVITY_NEW_TASK or specific android:launchMode <activity
 android:label="NewDocument"
 android:documentLaunchMode="none"
 android:name=".NewDocumentActivity" />
  90. Multitasking inside an app Create new document Even with FLAG_ACTIVITY_NEW_DOCUMENT,

    no new document task <activity
 android:label="NewDocument"
 android:documentLaunchMode="never"
 android:name=".NewDocumentActivity" />
  91. Multitasking inside an app Limiting the history Define the max

    number of recent documents for one activity Between 1 and 50, default to 16 <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:maxRecents="50"
 android:name=".NewDocumentActivity" />
  92. Multitasking inside an app Starting in background

  93. Multitasking inside an app Starting in background Force the grouping

    of documents with the original activity Force the behaviour of the android:documentLaunchMode=“always” final ActivityOptions options = ActivityOptions.makeTaskLaunchBehind();
 final Intent intent = new Intent(this, NewDocumentActivity.class)
 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
 startActivity(intent, options.toBundle());
  94. Multitasking inside an app In ActivityManager: ! public List<ActivityManager.AppTask> getAppTasks()

    Managing documents
  95. Multitasking inside an app ActivityManager.AppTask Contains a lot of information

    on a task, you can: ! public RecentTaskInfo getTaskInfo() ! public void moveToFront() ! public void finishAndRemoveTask() Managing documents
  96. Multitasking inside an app In a document activity: • finish()

    will terminate the activity but let the task remain in overview • finishAndRemoveTask() will terminate the activity and remove the task in overview Removing a document
  97. Multitasking inside an app Use FLAG_ACTIVITY_NEW_DOCUMENT when launching an external

    activity on 5.0+ On pre-5.0, use FLAG_ACTIVITY_CLEAR_TASK_WHEN_RESET to get roughly the same behaviour Compatibility
  98. Multitasking inside an app ActivityManager.TaskDescription Customising the overview entry of

    your activity: ! setTaskDescription(new ActivityManager.TaskDescription( "New label”, bitmap, getColor(R.color.red))); Overview screen customisation (5.0+)
  99. Multitasking inside an app Overview screen customisation (5.0+)

  100. Multitasking inside an app Persistance behaviours (5.0+) Default value Persist

    the activity only if it’s the root of the task <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:persistableMode="persistRootOnly"
 android:name=".NewDocumentActivity" />
  101. Multitasking inside an app Persistance behaviours (5.0+) If the activity

    is the root of the task, the task is not persisted <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:persistableMode="persistNever"
 android:name=".NewDocumentActivity" />
  102. Multitasking inside an app Persistance behaviours (5.0+) The activity is

    persisted across reboot Uses a PersistableBundle in onCreate/onSavedInstanceState to restore/ persist some data across reboot <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:persistableMode="persistAcrossReboots"
 android:name=".NewDocumentActivity" />
  103. Choose the behaviour you want wisely

  104. Choose the behaviour you want wisely New task only when

    there is a break in the app flow
  105. API is complicated, but powerful

  106. Credits ! • Nexus 5 frame - Cyril Mottier •

    Fonts: Lato, Menlo