Tag Archives: Learn

People of AI – Season 3

Posted by Ashley Oldacre

If you are joining us for the first time, you can binge listen to Seasons 1 and 2 wherever you get your podcasts.

We are back for another season of People of AI with a new lineup of incredible guests! I am so excited to continue co-hosting with Luiz Gustavo Martins as we meet inspiring people with interesting stories in the field of Artificial Intelligence.

Last season we focused on the big shift in technology spurred on by Generative AI. Fast forward 12 months, with the launch of multimodal models, we are at an interesting point in history.

In Season 3, we will continue to uncover our guests' personal and professional journeys into the field of AI, highlighting the important work/products they are focusing on. At the same time we want to dig deeper into the societal implications of what our guests create. We will ask questions to understand how they are leveraging AI to solve problems and create new experiences while also looking to understand what challenges they may face and what potential this technology has for both good and bad. We want to hold both truths to light through conversations with our guests. All this with the goal of aligning our technology with the public narrative and paint a realistic picture of how this technology is being used, the amazing things we can do with it and the right questions to make sure it is used safely and responsibly.

Starting today, we will release one new episode of season 3 per week. alternating video and audio. Listen to the first episode on the People of AI site or wherever you get your podcasts.

  • Episode 1: meet Adrit Rao, a 16 year old high school student, app developer, and research intern at Stanford University. We talk about App development and how learning about TensorFlow enabled him to create life changing apps in Healthcare. 
  • Episode 2: meet Indira Negi, a Product and Tech Executive investing in Medical Devices, AI and Digital health at the Bill and Melinda Gates Foundation as we learn about the latest investments in AI and Healthcare.
    • Episode 3: meet Tris Warkentin, Director of Product Management at Google Deepmind as we talk about the exciting new launches from Google’s latest Large Language Models. 
    • Episode 4: meet Kathleen Kenealy, Senior Software Engineer at Google DeepMind as we learn about the engineering genius behind Google’s latest Large Language Model launches. 
    • Episode 5: meet Jeanine Banks, Vice President and General Manager of Google Developer X and Head of Developer Relations. Join us as we learn about Google’s latest AI innovations and how they will change the developer landscape. 
    • Episode 6: meet François Chollet, creator of Keras and senior Software Engineer and AI researcher at Google. Join us as we learn about Google’s latest AI innovations and how they will change the developer landscape. 
    • Episode 7: meet Chansung Park, Google Developer Expert and Researcher as we talk about the importance of building and planning for Large Language Model Infrastructure. 
    • Episode 8: meet Fergus Hurley and Nia Castelly, co-founders of Checks, a privacy platform for mobile app developers that helps create a safer digital ecosystem by simplifying the path to privacy compliance for development teams and the apps they’re building. 
    • Episode 9: meet Sam Sepah and Thad Starner, as they talk about leveraging the power of Generative AI to unlock sign language capabilities.

    Listen now to the first episode of Season 3. We can’t wait to share the stories of these exceptional People of AI with you!


    This podcast is sponsored by Google. Any remarks made by the speakers are their own and are not endorsed by Google.

    Kubernetes 1.29 is available in the Regular channel of GKE

    Kubernetes 1.29 is now available in the GKE Regular Channel since January 26th, and was available in the Rapid Channel January 11th, less than 30 days after the OSS release! For more information about the content of Kubernetes 1.29, read the Kubernetes 1.29 Release Notes.

    New Features

    Using CEL for Validating Admission Policy

    Validating admission policies offer a declarative, in-process alternative to validating admission webhooks.

    Validating admission policies use the Common Expression Language (CEL) to declare the validation rules of a policy. Validation admission policies are highly configurable, enabling policy authors to define policies that can be parameterized and scoped to resources as needed by cluster administrators. [source]

    Validating Admission Policy graduates to beta in 1.29. We are especially excited about the work that Googlers Cici Huang, Joe Betz, and Jiahui Feng have led in this release to get to the beta milestone. As we move toward v1, we are actively working to ensure scalability and would appreciate any end-user feedback. [public doc here for those interested]

    The beta of ValidatingAdmissionPolicy feature can be opted into by enabling the beta APIs.

    InitContainers as a Sidecar

    InitContainers can now be configured as sidecar containers and kept running alongside normal containers in a Pod. This is only supported by nodes running version 1.29 or later, so ensure all nodes in a cluster are at version 1.29 or later before using this feature in Pods. The feature was long awaited. This is evident by the fact that Istio has already widely tested it and the Istio community working hard to make sure that the enablement of it can be done early with minimal disruption for the clusters with older nodes. You can participate in the discussion here.

    A big driver to deliver the feature is the growing number of AI/ML workloads which are often represented by Pods running to completion. Thos Pods need infrastructure sidecars - Istio and GCSFuse are examples of it, and Google recognizes this trend.

    Implementation of sidecar containers is and continues to be the community effort. We are proud to highlight that Googler Sergey Kanzhelev is driving it via the Sidecar working group, and it was a great effort of many other Googlers to make sure this KEP landed so fast. John Howard made sure the early versions of implementation were tested with Istio, Wojciech Tyczyński made sure the safe rollout vie production readiness review, Tim Hockin spent many hours in API review of the feature, and Clayton Coleman gave advice and helped with code reviews.

    New APIs

    API Priority and Fairness/Flow Control

    We are super excited to share that API Priority and Fairness graduated to Stable V1 / GA in 1.29! Controlling the behavior of the Kubernetes API server in an overload situation is a key task for cluster administrators, and this is what APF addresses. This ambitious project was initiated by Googler and founding API Machinery SIG lead Daniel Smith, and expanded to become a community-wide effort. Special thanks to Googler Wojciech Tyczyński and API Machinery members Mike Spreitzer from IBM and Abu Kashem from RedHat, for landing this critical feature in Kubernetes 1.29 (more details in the Kubernetes publication). In Google GKE we tested and utilized it early. In fact, any version above 1.26.4 is setting higher kubelet QPS values trusting the API server to handle it gracefully.

    Deprecations and Removals

    • The previously deprecated v1beta2 Priority and Fairness APIs are no longer served in 1.29, so update usage to v1beta3 before upgrading to 1.29.
    • With the API Priority and Fairness graduation to v1, the v1beta3 Priority and Fairness APIs are newly deprecated in 1.29, and will no longer be served in 1.32.
    • In the Node API, take a look at the changes to the status.kubeProxyVersion field, which will not be populated starting in v1.33. The field is currently populated with the kubelet version, not the kube-proxy version, and might not accurately reflect the kube-proxy version in use. For more information, see KEP-4004.
    • 1.29 removed support for the insecure SHA1 algorithm. To prevent impact on your clusters, you must replace incompatible certificates of webhook servers and extension API servers before upgrading your clusters to version 1.29.
      • GKE will not auto-upgrade clusters with webhook backends using incompatible certificates to 1.29 until you replace the certificates or until version 1.28 reaches end of life. For more information refer to the official GKE documentation.
    • The Ceph CephFS (kubernetes.io/cephfs) and RBD (kubernetes.io/rbd) volume plugins are deprecated since 1.28 and will be removed in a future release

    Shoutout to the Production Readiness Review (PRR) team

    For each new Kubernetes Release, there is a dedicated sub group of SIG Architecture, composed of very senior contributors in the Kubernetes Community, that regularly conducts Production Readiness reviews for each new release, going through each feature.

    • OSS Production Readiness Reviews (PRR) reduce toil for all the different Cloud Providers, by shifting the effort onto OSS developers.
    • OSS Production Readiness Reviews surface production safety, observability, and scalability issues with OSS features at design time, when it is still possible to affect the outcomes.
    • By ensuring feature gates, solid enable → disable → enable testing, and attention to upgrade and rollout considerations, OSS Production Readiness Reviews enable rapid mitigation of failures in new features.

    As part of this group, we want to thank Googlers John Belamaric and Wojciech Tyczyński for doing this remarkable, heavy lifting on non shiny, and often invisible work. Additionally, we’d like to congratulate Googler Joe Betz who recently graduated as a new PRR reviewer, after shadowing during all 2023 the process.

    By Jordan Liggitt, Jago Macleod, Sergey Kanzhelev, and Federico Bongiovanni – Google Kubernetes Kernel team

    How it’s Made – Exploring AI x Learning through ShiffBot, an AI experiment powered by the Gemini API

    Posted by Jasmin Rubinovitz, AI Researcher

    Google Lab Sessions is a series of experimental collaborations with innovators. In this session, we partnered with beloved creative coding educator and YouTube creator Daniel Shiffman. Together, we explored some of the ways AI, and specifically the Gemini API, could provide value to teachers and students during the learning process.

    Dan Shiffman started out teaching programming courses at NYU ITP and later created his YouTube channel The Coding Train, making his content available to a wider audience. Learning to code can be challenging, sometimes even small obstacles can be hard to overcome when you are on your own. So together with Dan we asked - could we try and complement his teaching even further by creating an AI-powered tool that can help students while they are actually coding, in their coding environment?

    Dan uses the wonderful p5.js JavaScript library and its accessible editor to teach code. So we set out to create an experimental chrome extension for the editor, that brings together Dan’s teaching style as well as his various online resources into the coding environment itself.

    In this post, we'll share how we used the Gemini API to craft Shiffbot with Dan. We're hoping that some of the things we learned along the way will inspire you to create and build your own ideas.

    To learn more about ShiffBot visit - shiffbot.withgoogle.com

    As we started defining and tinkering with what this chatbot might be, we found ourselves faced with two key questions:

    1. How can ShiffBot inspire curiosity, exploration, and creative expression in the same way that Dan does in his classes and videos?
    2. How can we surface the variety of creative-coding approaches, and surface the deep knowledge of Dan and the community?

    Let’s take a look at how we approached these questions by combining Google Gemini API’s capabilities across prompt engineering for Dan’s unique teaching style, alongside embeddings and semantic retrieval with Dan’s collection of educational content.


    Tone and delivery: putting the “Shiff” in “ShiffBot”

    A text prompt is a thoughtfully designed textual sequence that is used to prime a Large Language Model (LLM) to generate text in a certain way. Like many AI applications, engineering the right prompt was a big part of sculpting the experience.

    Whenever a user asks ShiffBot a question, a prompt is constructed in real time from a few different parts; some are static and some are dynamically generated alongside the question.

    ShiffBot prompt building blocks
    ShiffBot prompt building blocks (click to enlarge)

    The first part of the prompt is static and always the same. We worked closely with Dan to phrase it and test many texts, instructions and techniques. We used Google AI Studio, a free web-based developer tool, to rapidly test multiple prompts and potential conversations with ShiffBot.

    ShiffBot’s prompt starts with setting the bot persona and defining some instructions and goals for it to follow. The hope was to both create continuity for Dan’s unique energy, as seen in his videos, and also adhere to the teaching principles that his students and fans adore.

    We were hoping that ShiffBot could provide encouragement, guidance and access to relevant high-quality resources. And, specifically, do it without simply providing the answer, but rather help students discover their own answers (as there can be more than one).

    The instructions draw from Dan’s teaching style by including sentences like “ask the user questions” because that’s what Dan is doing in the classroom. This is a part of the persona / instructions part of the prompt:

    You are a ShiffBot, a chat bot embedded in the p5.js web editor that can help users while they learn creative coding. You are based on Daniel Shiffman's personality and The Coding Train YouTube channel. You are playful, silly, friendly, and educational. You like to make fun of yourself and your mission is to support the creative coding process and help the user feel less alone while coding. You love humans, are fascinated by them, and want to learn more about humans. You just LOVE to help them with their code, it brings you extraordinary happiness...

    The next piece of the prompt utilizes another capability of LLMs called few-shot learning. It means that with just a small number of examples, the model learns patterns and can then use those in new inputs. Practically, as part of the prompt, we provide a number of demonstrations of input and expected output.

    We worked with Dan to create a small set of such few-shot examples. These are pairs of <user-input><bot-response> where the <bot-response> is always in our desired ShiffBot style. It looks like this:

    User: How do I draw a circle? 

    ShiffBot: Oh! 🟣 🟢 🔵 That's what the `circle()` function is for! You can think of a function as a command: "I command you computer-machine to draw a circle!" You have to also specify where to draw the circle and how big (that's what "arguments" to a function are!), the p5.js reference which was written by actual, beautiful humans can tell you more! 

    --- 

    User: My homework is to draw a self-portrait. ShiffBot: Ooh! Self portraits are so fun! You could draw shapes to represent you, but you should be creative and think abstractly! What does a self-portrait mean to you? Try starting with `square()` or `circle()` and let me know how it goes! 🎨

    Our prompt includes 13 such pairs.

    Another thing we noticed as we were working on the extension is that sometimes, giving more context in the prompt helps. In the case of learning creative coding in p5.js, explaining some p5.js principles in the prompt guides the model to use those principles as it answers the user’s question. So we also include those things like:

    Use let rather than var or const.

    Everything we discussed up to now is static, meaning that it remains the same for every turn of the conversation between the user and ShiffBot. Now let's explore some of the parts that are constructed dynamically as the conversation evolves.


    Conversation and code context

    Because ShiffBot is embedded inside the p5.js editor, it can “see” the current code the user is working on, so that it can generate responses that are more personalized and relevant. We grab that information for the HTML DOM and append it to the prompt as well.

    the p5.js editor environment
    the p5.js editor environment (click to enlarge)

    Then, the full conversation history is appended, e.g:

    ShiffBot: ☀️ Good morning! Is it morning? I wouldn't know. I'm just a robot. What are you coding today? User: how do I draw a square?

    ShiffBot: Squares! I love squares! They are so stable and reliable. To draw a square, you can use the square() function. It takes four arguments: the x and y coordinates of the top-left corner, and the width and height of the square. For example, square(10, 10, 50, 50) would draw a square with its top-left corner at (10, 10) and a width and height of 50 pixels.

    User: how can I change the color?

    ShiffBot:

    We make sure to end with

    ShiffBot:

    So the model understands that it now needs to complete the next piece of the conversation by ShiffBot.


    Semantic Retrieval: grounding the experience in p5.js resources and Dan’s content

    Dan has created a lot of material over the years, including over 1,000 YouTube videos, books and code examples. We wanted to have ShiffBot surface these wonderful materials to learners at the right time. To do so, we used the Semantic Retrieval feature in the Gemini API, which allows you to create a corpus of text pieces, and then send it a query and get the texts in your corpus that are most relevant to your query. (Behind the scenes, it uses a cool thing called text embeddings; you can read more about embeddings here.) For ShiffBot we created corpuses from Dan’s content so that we could add relevant content pieces to the prompt as needed, or show them in the conversation with ShiffBot.


    Creating a Corpus of Videos

    In The Coding Train videos, Dan explains many concepts, from simple to advanced, and runs through coding challenges. Ideally ShiffBot could use and present the right video at the right time.

    The Semantic Retrieval in Gemini API allows users to create multiple corpuses. A corpus is built out of documents, and each document contains one or more chunks of text. Documents and chunks can also have metadata fields for filtering or storing more information.

    In Dan’s video corpus, each video is a document and the video url is saved as a metadata field along with the video title. The videos are split into chapters (manually by Dan as he uploads them to YouTube). We used each chapter as a chunk, with the text for each chunk being

    <videoTitle>

    <videoDescription>

    <chapterTitle>

    <transcriptText>

    We use the video title, the first line of the video description and chapter title to give a bit more context for the retrieval to work.

    This is an example of a chunk object that represents the R, G, B chapter in this video.

    1.4: Color - p5.js Tutorial


    In this video I discuss how color works: RGB color, fill(), stroke(), and transparency.


    Chapter 1: R, G, B


    R stands for red, g stands for green, b stands for blue. The way that you create a digital color is by mixing some amount of red, some amount of green, and some amount of blue. So that's that that's where I want to start. But that's the concept, how do I apply that concept to function names, and arguments of those functions? Well, actually, guess what? We have done that already. In here, there is a function that is talking about color. Background is a function that draws a solid color over the entire background of the canvas. And there is, somehow, 220 sprinkles of red, zero sprinkles of green, right? RGB, those are the arguments. And 200 sprinkles of blue. And when you sprinkle that amount of red, and that amount of blue, you get this pink. But let's just go with this. What if we take out all of the blue? You can see that's pretty red. What if I take out all of the red? Now it's black. What if I just put some really big numbers in here, like, just guess, like, 1,000? Look at that. Now we've got white, so all the colors all mixed together make white. That's weird, right? Because if you, like, worked with paint, and you were to mix, like, a whole lot of paint together, you get this, like, brown muddy color, get darker and darker. This is the way that the color mixing is working, here. It's, like, mixing light. So the analogy, here, is I have a red flashlight, a green flashlight, and a blue flashlight. And if I shine all those flashlights together in the same spot, they mix together. It's additive color, the more we add up all those colors, the brighter and brighter it gets. But, actually, this is kind of wrong, the fact that I'm putting 1,000 in here. So the idea, here, is we're sprinkling a certain amount of red, and a certain amount of green, and a certain amount of blue. And by the way, there are other ways to set color, but I'll get to that. This is not the only way, because some of you watching, are like, I heard something about HSB color. And there's all sorts of other ways to do it, but this is the fundamental, basic way. The amount that I can sprinkle has a range. No red, none more red, is zero. The maximum amount of red is 255. By the way, how many numbers are there between 0 and 255 if you keep the 0? 0, 1, 2, 3, 4-- it's 256. Again, we're back to this weird counting from zero thing. So there's 256 possibilities, 0 through 255. So, now, let's come back to this and see. All right, let's go back to zero, 0, 0, 0. Let's do 255, we can see that it's blue. Let's do 100,000, it's the same blue. So p5 is kind of smart enough to know when you call the background function, if you by accident put a number in there that's bigger than 255, just consider it 255. Now, you can customize those ranges for yourself, and there's reasons why you might want to do that. Again, I'm going to come back to that, you can look up the function color mode for how to do that. But let's just stay with the default, a red, a green, and a blue. So, I'm not really very talented visual design wise. So I'm not going to talk to you about how to pick beautiful colors that work well together. You're going to have that talent yourself, I bet. Or you might find some other resources. But this is how it works, RGB. One thing you might notice is, did you notice how when they were all zero, it was black, and they were all 255 it was white? What happens if I make them all, like, 100? It's, like, this gray color. When r equals g equals b, when the red, green, and blue values are all equal, this is something known as grayscale color.

    When the user asks ShiffBot a question, the question is embedded to a numerical representation, and Gemini’s Semantic Retrieval feature is used to find the texts whose embeddings are closest to the question. Those relevant video transcripts and links are added to the prompt - so the model could use that information when generating an answer (and potentially add the video itself into the conversation).

    Semantic Retrieval Graph
    Semantic Retrieval Graph (click to enlarge)

    Creating a Corpus of Code Examples

    We do the same with another corpus of p5.js examples written by Dan. To create the code examples corpus, we used Gemini and asked it to explain what the code is doing. Those natural language explanations are added as chunks to the corpus, so that when the user asks a question, we try to find matching descriptions of code examples, the url to the p5.js sketch itself is saved in the metadata, so after retrieving the code itself along with the sketch url is added in the prompt.

    To generate the textual description, Gemini was prompted with:

    The following is a p5.js sketch. Explain what this code is doing in a short simple way.

    code:

    ${sketchCode}


    Example for a code chunk:
    Text:
     

    Arrays - Color Palette

    This p5.js sketch creates a color palette visualization. It first defines an array of colors and sets up a canvas. Then, in the draw loop, it uses a for loop to iterate through the array of colors and display them as rectangles on the canvas. The rectangles are centered on the canvas and their size is determined by the value of the blockSize variable.

    The sketch also displays the red, green, and blue values of each color below each rectangle.

    Finally, it displays the name of the palette at the bottom of the canvas.

    Related video: 7.1: What is an array? - p5.js Tutorial - This video covers the basics on using arrays in JavaScript. What do they look like, how do they work, when should you use them?

    Moving image showing constructing the ShiffBot prompt
    Constructing the ShiffBot prompt (click to enlarge)

    Other ShiffBot Features Implemented with Gemini

    Beside the long prompt that is running the conversation, other smaller prompts are used to generate ShiffBot features.


    Seeding the conversation with content pre-generated by Gemini

    ShiffBot greetings should be welcoming and fun. Ideally they make the user smile, so we started by thinking with Dan what could be good greetings for ShiffBot. After phrasing a few examples, we use Gemini to generate a bunch more, so we can have a variety in the greetings. Those greetings go into the conversation history and seed it with a unique style, but make ShiffBot feel fun and new every time you start a conversation. We did the same with the initial suggestion chips that show up when you start the conversation. When there’s no conversation context yet, it’s important to have some suggestions of what the user might ask. We pre-generated those to seed the conversation in an interesting and helpful way.


    Dynamically Generated Suggestion Chips

    Suggestion chips during the conversation should be relevant for what the user is currently trying to do. We have a prompt and a call to Gemini that are solely dedicated to generating the suggested questions chips. In this case, the model’s only task is to suggest followup questions for a given conversation. We also use the few-shot technique here (the same technique we used in the static part of the prompt described above, where we include a few examples for the model to learn from). This time the prompt includes some examples for good suggestions, so that the model could generalize to any conversation:

    Given a conversation between a user and an assistant in the p5js framework, suggest followup questions that the user could ask.

    Return up to 4 suggestions, separated by the ; sign.

    Avoid suggesting questions that the user already asked. The suggestions should only be related to creative coding and p5js.


    Examples:

    ShiffBot: Great idea! First, let's think about what in the sketch could be an object! What do you think?

    Suggestions: What does this code do?; What's wrong with my code?; Make it more readable please


    User: Help!

    ShiffBot: How can I help?

    Suggestions: Explain this code to me; Give me some ideas; Cleanup my code

    suggested response chips, generated by Gemini
    suggested response chips, generated by Gemini (click to enlarge)

    Final thoughts and next steps

    ShiffBot is an example of how you can experiment with the Gemini API to build applications with tailored experiences for and with a community.

    We found that the techniques above helped us bring out much of the experience that Dan had in mind for his students during our co-creation process. AI is a dynamic field and we’re sure your techniques will evolve with it, but hopefully they are helpful to you as a snapshot of our explorations and towards your own. We are also excited for things to come both in terms of Gemini and API tools that broaden human curiosity and creativity.

    For example, we’ve already started to explore how multimodality can help students show ShiffBot their work and the benefits that has on the learning process. We’re now learning how to weave it into the current experience and hope to share it soon.

    experimental exploration of multimodality in ShiffBot
    experimental exploration of multimodality in ShiffBot (click to enlarge)

    Whether for coding, writing and even thinking, creators play a crucial role in helping us imagine what these collaborations might look like. Our hope is that this Lab Session gives you a glimpse of what’s possible using the Gemini API, and inspires you to use Google’s AI offerings to bring your own ideas to life, in whatever your craft may be.

    Create smart chips for link previewing in Google Docs

    Posted by Chanel Greco, Developer Advocate

    Earlier this year, we announced the general availability of third-party smart chips in Google Docs. This new feature lets you add, view, and engage with critical information from third party apps directly in Google Docs. Several partners, including Asana, Atlassian, Figma, Loom, Miro, Tableau, and Whimsical, have already created smart chips so users can start embedding content from their apps directly into Docs. Sourabh Choraria, a Google Developer Expert for Google Workspace and hobby developer, published a third-party smart chip solution called “Link Previews” to the Google Workspace Marketplace. This app adds information to Google Docs from multiple commonly used SaaS tools.

    In this blog post you will find out how you too can create your own smart chips for Google Docs.

    Example of a smart chip that was created to preview information from an event management system
    Example of a smart chip that was created to preview information from an event management system


    Understanding how smart chips for third-party services work

    Third-party smart chips are powered by Google Workspace Add-ons and can be published to the Google Workspace Marketplace. From there, an admin or user can install the add-on and it will appear in the sidebar on the right hand side of Google Docs.

    The Google Workspace Add-on detects a service's links and prompts Google Docs users to preview them. This means that you can create smart chips for any service that has a publicly accessible URL. You can configure an add-on to preview multiple URL patterns, such as links to support cases, sales leads, employee profiles, and more. This configuration is done in the add-on’s manifest file.

    {
      "timeZone": "America/Los_Angeles",
      "exceptionLogging": "STACKDRIVER",
      "runtimeVersion": "V8",
      "oauthScopes": [
        "https://www.googleapis.com/auth/workspace.linkpreview",
        "https://www.googleapis.com/auth/script.external_request"
      ],
      "addOns": {
        "common": {
          "name": "Preview Books Add-on",
          "logoUrl": "https://developers.google.com/workspace/add-ons/images/library-icon.png",
          "layoutProperties": {
            "primaryColor": "#dd4b39"
          }
        },
        "docs": {
          "linkPreviewTriggers": [
            {
              "runFunction": "bookLinkPreview",
              "patterns": [
                {
                  "hostPattern": "*.google.*",
                  "pathPrefix": "books"
                },
                {
                  "hostPattern": "*.google.*",
                  "pathPrefix": "books/edition"
                }
              ],
              "labelText": "Book",
              "logoUrl": "https://developers.google.com/workspace/add-ons/images/book-icon.png",
              "localizedLabelText": {
                "es": "Libros"
              }
            }
          ]
        }
      }
    }
    
    The manifest file contains the URL pattern for the Google Books API

    The smart chip displays an icon and short title or description of the link's content. When the user hovers over the chip, they see a card interface that previews more information about the file or link. You can customize the card interface that appears when the user hovers over a smart chip. To create the card interface, you use widgets to display information about the link. You can also build actions that let users open the link or modify its contents. For a list of all the supported components for preview cards check the developer documentation.

    function getBook(id) {
    // Code to fetch the data from the Google Books API
    }
    
    function bookLinkPreview(event) {
     if (event.docs.matchedUrl.url) {
    // Through getBook(id) the relevant data is fetched and used to build the smart chip and card
    
        const previewHeader = CardService.newCardHeader()
          .setSubtitle('By ' + bookAuthors)
          .setTitle(bookTitle);
    
        const previewPages = CardService.newDecoratedText()
          .setTopLabel('Page count')
          .setText(bookPageCount);
    
        const previewDescription = CardService.newDecoratedText()
          .setTopLabel('About this book')
          .setText(bookDescription).setWrapText(true);
    
        const previewImage = CardService.newImage()
          .setAltText('Image of book cover')
          .setImageUrl(bookImage);
    
        const buttonBook = CardService.newTextButton()
          .setText('View book')
          .setOpenLink(CardService.newOpenLink()
            .setUrl(event.docs.matchedUrl.url));
    
        const cardSectionBook = CardService.newCardSection()
          .addWidget(previewImage)
          .addWidget(previewPages)
          .addWidget(CardService.newDivider())
          .addWidget(previewDescription)
          .addWidget(buttonBook);
    
        return CardService.newCardBuilder()
        .setHeader(previewHeader)
        .addSection(cardSectionBook)
        .build();
      }
    }
    
    This is the Apps Script code to create a smart chip.

    A smart chip hovered state.
    A smart chip hovered state. The data displayed is fetched from the Google for Developers blog post URL that was pasted by the user.


    For a detailed walkthrough of the code used in this post, please checkout the Preview links from Google Books with smart chips sample tutorial.



    How to choose the technology for your add-on

    When creating smart chips for link previewing, you can choose from two different technologies to create your add-on: Google Apps Script or alternate runtime.

    Apps script is a rapid application development platform that is built into Google Workspace. This fact makes Apps Script a good choice for prototyping and validating your smart chip solution as it requires no pre-existing development environment. But Apps Script isn’t only for prototyping as some developers choose to create their Google Workspace Add-on with it and even publish it to the Google Workspace Marketplace for users to install.

    If you want to create your smart chip with Apps Script you can check out the video below in which you learn how to build a smart chip for link previewing in Google Docs from A - Z. Want the code used in the video tutorial? Then have a look at the Preview links from Google Books with smart chips sample page.

    If you prefer to create your Google Workspace Add-on using your own development environment, programming language, hosting, packages, etc., then alternate runtime is the right choice. You can choose from different programming languages like Node.js, Java, Python, and more. The hosting of the add-on runtime code can be on any cloud or on premise infrastructure as long as runtime code can be exposed as a public HTTP(S) endpoint. You can learn more about how to create smart chips using alternate runtimes from the developer documentation.



    How to share your add-on with others

    You can share your add-on with others through the Google Workspace Marketplace. Let’s say you want to make your smart chip solution available to your team. In that case you can publish the add-on to your Google Workspace organization, also known as a private app. On the other hand, if you want to share your add-on with anyone who has a Google Account, you can publish it as a public app.

    To find out more about publishing to the Google Workspace Marketplace, you can watch this video that will walk you through the process.



    Getting started

    Learn more about creating smart chips for link previewing in the developer documentation. There you will find further information and code samples you can base your solution of. We can’t wait to see what smart chip solutions you will build.

    Create smart chips for link previewing in Google Docs

    Posted by Chanel Greco, Developer Advocate

    Earlier this year, we announced the general availability of third-party smart chips in Google Docs. This new feature lets you add, view, and engage with critical information from third party apps directly in Google Docs. Several partners, including Asana, Atlassian, Figma, Loom, Miro, Tableau, and Whimsical, have already created smart chips so users can start embedding content from their apps directly into Docs. Sourabh Choraria, a Google Developer Expert for Google Workspace and hobby developer, published a third-party smart chip solution called “Link Previews” to the Google Workspace Marketplace. This app adds information to Google Docs from multiple commonly used SaaS tools.

    In this blog post you will find out how you too can create your own smart chips for Google Docs.

    Example of a smart chip that was created to preview information from an event management system
    Example of a smart chip that was created to preview information from an event management system


    Understanding how smart chips for third-party services work

    Third-party smart chips are powered by Google Workspace Add-ons and can be published to the Google Workspace Marketplace. From there, an admin or user can install the add-on and it will appear in the sidebar on the right hand side of Google Docs.

    The Google Workspace Add-on detects a service's links and prompts Google Docs users to preview them. This means that you can create smart chips for any service that has a publicly accessible URL. You can configure an add-on to preview multiple URL patterns, such as links to support cases, sales leads, employee profiles, and more. This configuration is done in the add-on’s manifest file.

    {
      "timeZone": "America/Los_Angeles",
      "exceptionLogging": "STACKDRIVER",
      "runtimeVersion": "V8",
      "oauthScopes": [
        "https://www.googleapis.com/auth/workspace.linkpreview",
        "https://www.googleapis.com/auth/script.external_request"
      ],
      "addOns": {
        "common": {
          "name": "Preview Books Add-on",
          "logoUrl": "https://developers.google.com/workspace/add-ons/images/library-icon.png",
          "layoutProperties": {
            "primaryColor": "#dd4b39"
          }
        },
        "docs": {
          "linkPreviewTriggers": [
            {
              "runFunction": "bookLinkPreview",
              "patterns": [
                {
                  "hostPattern": "*.google.*",
                  "pathPrefix": "books"
                },
                {
                  "hostPattern": "*.google.*",
                  "pathPrefix": "books/edition"
                }
              ],
              "labelText": "Book",
              "logoUrl": "https://developers.google.com/workspace/add-ons/images/book-icon.png",
              "localizedLabelText": {
                "es": "Libros"
              }
            }
          ]
        }
      }
    }
    
    The manifest file contains the URL pattern for the Google Books API

    The smart chip displays an icon and short title or description of the link's content. When the user hovers over the chip, they see a card interface that previews more information about the file or link. You can customize the card interface that appears when the user hovers over a smart chip. To create the card interface, you use widgets to display information about the link. You can also build actions that let users open the link or modify its contents. For a list of all the supported components for preview cards check the developer documentation.

    function getBook(id) {
    // Code to fetch the data from the Google Books API
    }
    
    function bookLinkPreview(event) {
     if (event.docs.matchedUrl.url) {
    // Through getBook(id) the relevant data is fetched and used to build the smart chip and card
    
        const previewHeader = CardService.newCardHeader()
          .setSubtitle('By ' + bookAuthors)
          .setTitle(bookTitle);
    
        const previewPages = CardService.newDecoratedText()
          .setTopLabel('Page count')
          .setText(bookPageCount);
    
        const previewDescription = CardService.newDecoratedText()
          .setTopLabel('About this book')
          .setText(bookDescription).setWrapText(true);
    
        const previewImage = CardService.newImage()
          .setAltText('Image of book cover')
          .setImageUrl(bookImage);
    
        const buttonBook = CardService.newTextButton()
          .setText('View book')
          .setOpenLink(CardService.newOpenLink()
            .setUrl(event.docs.matchedUrl.url));
    
        const cardSectionBook = CardService.newCardSection()
          .addWidget(previewImage)
          .addWidget(previewPages)
          .addWidget(CardService.newDivider())
          .addWidget(previewDescription)
          .addWidget(buttonBook);
    
        return CardService.newCardBuilder()
        .setHeader(previewHeader)
        .addSection(cardSectionBook)
        .build();
      }
    }
    
    This is the Apps Script code to create a smart chip.

    A smart chip hovered state.
    A smart chip hovered state. The data displayed is fetched from the Google for Developers blog post URL that was pasted by the user.


    For a detailed walkthrough of the code used in this post, please checkout the Preview links from Google Books with smart chips sample tutorial.



    How to choose the technology for your add-on

    When creating smart chips for link previewing, you can choose from two different technologies to create your add-on: Google Apps Script or alternate runtime.

    Apps script is a rapid application development platform that is built into Google Workspace. This fact makes Apps Script a good choice for prototyping and validating your smart chip solution as it requires no pre-existing development environment. But Apps Script isn’t only for prototyping as some developers choose to create their Google Workspace Add-on with it and even publish it to the Google Workspace Marketplace for users to install.

    If you want to create your smart chip with Apps Script you can check out the video below in which you learn how to build a smart chip for link previewing in Google Docs from A - Z. Want the code used in the video tutorial? Then have a look at the Preview links from Google Books with smart chips sample page.

    If you prefer to create your Google Workspace Add-on using your own development environment, programming language, hosting, packages, etc., then alternate runtime is the right choice. You can choose from different programming languages like Node.js, Java, Python, and more. The hosting of the add-on runtime code can be on any cloud or on premise infrastructure as long as runtime code can be exposed as a public HTTP(S) endpoint. You can learn more about how to create smart chips using alternate runtimes from the developer documentation.



    How to share your add-on with others

    You can share your add-on with others through the Google Workspace Marketplace. Let’s say you want to make your smart chip solution available to your team. In that case you can publish the add-on to your Google Workspace organization, also known as a private app. On the other hand, if you want to share your add-on with anyone who has a Google Account, you can publish it as a public app.

    To find out more about publishing to the Google Workspace Marketplace, you can watch this video that will walk you through the process.



    Getting started

    Learn more about creating smart chips for link previewing in the developer documentation. There you will find further information and code samples you can base your solution of. We can’t wait to see what smart chip solutions you will build.

    Faster Rust Toolchains for Android

    Posted by Chris Wailes - Senior Software Engineer

    The performance, safety, and developer productivity provided by Rust has led to rapid adoption in the Android Platform. Since slower build times are a concern when using Rust, particularly within a massive project like Android, we've worked to ship the fastest version of the Rust toolchain that we can. To do this we leverage multiple forms of profiling and optimization, as well as tuning C/C++, linker, and Rust flags. Much of what I’m about to describe is similar to the build process for the official releases of the Rust toolchain, but tailored for the specific needs of the Android codebase. I hope that this post will be generally informative and, if you are a maintainer of a Rust toolchain, may make your life easier.

    Android’s Compilers

    While Android is certainly not unique in its need for a performant cross-compiling toolchain this fact, combined with the large number of daily Android build invocations, means that we must carefully balance tradeoffs between the time it takes to build a toolchain, the toolchain’s size, and the produced compiler’s performance.

    Our Build Process

    To be clear, the optimizations listed below are also present in the versions of rustc that are obtained using rustup. What differentiates the Android toolchain from the official releases, besides the provenance, are the cross-compilation targets available and the codebase used for profiling. All performance numbers listed below are the time it takes to build the Rust components of an Android image and may not be reflective of the speedup when compiling other codebases with our toolchain.

    Codegen Units (CGU1)

    When Rust compiles a crate it will break it into some number of code generation units. Each independent chunk of code is generated and optimized concurrently and then later re-combined. This approach allows LLVM to process each code generation unit separately and improves compile time but can reduce the performance of the generated code. Some of this performance can be recovered via the use of Link Time Optimization (LTO), but this isn’t guaranteed to achieve the same performance as if the crate were compiled in a single codegen unit.

    To expose as many opportunities for optimization as possible and ensure reproducible builds we add the -C codegen-units=1 option to the RUSTFLAGS environment variable. This reduces the size of the toolchain by ~5.5% while increasing performance by ~1.8%.

    Be aware that setting this option will slow down the time it takes to build the toolchain by ~2x (measured on our workstations).

    GC Sections

    Many projects, including the Rust toolchain, have functions, classes, or even entire namespaces that are not needed in certain contexts. The safest and easiest option is to leave these code objects in the final product. This will increase code size and may decrease performance (due to caching and layout issues), but it should never produce a miscompiled or mislinked binary.

    It is possible, however, to ask the linker to remove code objects that aren’t transitively referenced from the main()function using the --gc-sections linker argument. The linker can only operate on a section-basis, so, if any object in a section is referenced, the entire section must be retained. This is why it is also common to pass the -ffunction-sections and -fdata-sections options to the compiler or code generation backend. This will ensure that each code object is given an independent section, thus allowing the linker’s garbage collection pass to collect objects individually.

    This is one of the first optimizations we implemented and, at the time, it produced significant size savings (on the order of 100s of MiBs). However, most of these gains have been subsumed by those made from setting -C codegen-units=1 when they are used in combination and there is now no difference between the two produced toolchains in size or performance. However, due to the extra overhead, we do not always use CGU1 when building the toolchain. When testing for correctness the final speed of the compiler is less important and, as such, we allow the toolchain to be built with the default number of codegen units. In these situations we still run section GC during linking as it yields some performance and size benefits at a very low cost.

    Link-Time Optimization (LTO)

    A compiler can only optimize the functions and data it can see. Building a library or executable from independent object files or libraries can speed up compilation but at the cost of optimizations that depend on information that’s only available when the final binary is assembled. Link-Time Optimization gives the compiler another opportunity to analyze and modify the binary during linking.

    For the Android Rust toolchain we perform thin LTO on both the C++ code in LLVM and the Rust code that makes up the Rust compiler and tools. Because the IR emitted by our clang might be a different version than the IR emitted by rustc we can’t perform cross-language LTO or statically link against libLLVM. The performance gains from using an LTO optimized shared library are greater than those from using a non-LTO optimized static library however, so we’ve opted to use shared linking.

    Using CGU1, GC sections, and LTO produces a speedup of ~7.7% and size improvement of ~5.4% over the baseline. This works out to a speedup of ~6% over the previous stage in the pipeline due solely to LTO.

    Profile-Guided Optimization (PGO)

    Command line arguments, environment variables, and the contents of files can all influence how a program executes. Some blocks of code might be used frequently while other branches and functions may only be used when an error occurs. By profiling an application as it executes we can collect data on how often these code blocks are executed. This data can then be used to guide optimizations when recompiling the program.

    We use instrumented binaries to collect profiles from both building the Rust toolchain itself and from building the Rust components of Android images for x86_64, aarch64, and riscv64. These four profiles are then combined and the toolchain is recompiled with profile-guided optimizations.

    As a result, the toolchain achieves a ~19.8% speedup and 5.3% reduction in size over the baseline compiler. This is a 13.2% speedup over the previous stage in the compiler.

    BOLT: Binary Optimization and Layout Tool

    Even with LTO enabled the linker is still in control of the layout of the final binary. Because it isn’t being guided by any profiling information the linker might accidentally place a function that is frequently called (hot) next to a function that is rarely called (cold). When the hot function is later called all functions on the same memory page will be loaded. The cold functions are now taking up space that could be allocated to other hot functions, thus forcing the additional pages that do contain these functions to be loaded.

    BOLT mitigates this problem by using an additional set of layout-focused profiling information to re-organize functions and data. For the purposes of speeding up rustc we profiled libLLVM, libstd, and librustc_driver, which are the compiler’s main dependencies. These libraries are then BOLT optimized using the following options:

    --peepholes=all
    --data=<path-to-profile>
    --reorder-blocks=ext-tsp
    –-reorder-functions=hfsort
    --split-functions
    --split-all-cold
    --split-eh
    --dyno-stats

    Any additional libraries matching lib/*.so are optimized without profiles using only --peepholes=all.

    Applying BOLT to our toolchain produces a speedup over the baseline compiler of ~24.7% at a size increase of ~10.9%. This is a speedup of ~6.1% over the PGOed compiler without BOLT.

    If you are interested in using BOLT in your own project/build I offer these two bits of advice: 1) you’ll need to emit additional relocation information into your binaries using the -Wl,--emit-relocs linker argument and 2) use the same input library when invoking BOLT to produce the instrumented and the optimized versions.

    Conclusion

    Graph of normalized size and duration comparison between Toolchain size and Android Rust build time

    Optimizations

    Speedup vs Baseline
    Monolithic 1.8%
    Mono + GC Sections

    1.9%
    Mono + GC + LTO 7.7%
    Mono + GC + LTO + PGO 19.8%

    Mono + GC + LTO + PGO + BOLT

    24.7%

    By compiling as a single code generation unit, garbage collecting our data objects, performing both link-time and profile-guided optimizations, and leveraging the BOLT tool we were able to speed up the time it takes to compile the Rust components of Android by 24.8%. For every 50k Android builds per day run in our CI infrastructure we save ~10K hours of serial execution.

    Our industry is not one to stand still and there will surely be another tool and another set of profiles in need of collecting in the near future. Until then we’ll continue making incremental improvements in search of additional performance. Happy coding!

    Order Files in Android

    Posted by Aditya Kumar – Software Engineer

    Context

    Binary layout using a symbol order file (also known as binary order file or linker order file) is a well-known link-time optimization. The linker uses the order of symbols in order file to lay out symbols in the binary. Order file based binary layout improves application launch time as well as other critical user journeys. Order file generation is typically a multi-step process where developers use different tools at every stage. We are providing a unified set of tools and documentation that will allow every native app developer to leverage this optimization. Both Android app developers and the AOSP community can benefit from the tools.

    Background

    Source code is typically structured to facilitate software development and comprehension. The layout of functions and variables in a binary is also impacted by their relative ordering in the source code. The binary layout impacts application performance as the operating system has no way of knowing which symbols will be required in future and typically uses spatial locality as one of the cost models for prefetching subsequent pages.

    But the order of symbols in a binary may not reflect the program execution order. When an application executes, fetching symbols that are not present in memory would result in page faults. For example, consider the following program:

    // Test.cpp
    int foo() { /* */ } int bar() { /* */ } // Other functions... int main() { bar(); foo();

    }

    Which gets compiled into:

    # Test.app page_x: _foo page_y: _bar # Other symbols page_z:_main

    When Test.app starts, its entrypoint _main is fetched first then _bar followed by _foo. Executing Test.app can lead to page faults for fetching each function. Compare this to the following binary layout where all the functions are located in the same page (assuming the functions are small enough).

    # Test.app page_1: _main page_1: _bar page_1: _foo # Other symbols

    In this case when _main gets fetched, _bar and _foo can get fetched in the memory at the same time. In case these symbols are large and they are located in consecutive pages, there is a high chance the operating system may prefetch those pages resulting in less page faults.

    Because execution order of functions during an application lifecycle may depend on various factors it is impossible to have a unique order of symbols that is most efficient. Fortunately, application startup sequence is fairly deterministic and stable in general. And it is also possible to build a binary having a desired symbol order with the help of linkers like lld which is the default linker for Android NDK toolchain.

    Order file is a text file containing a list of symbols. The linker uses the order of symbols in order file to lay out symbols in the binary. An order file having functions that get called during the app startup sequence can reduce page faults resulting in improved launch time. Order files can improve the launch time of mobile applications by more than 2%. The benefits of order files are more meaningful on larger apps and lower end devices. A more mature order file generation system can improve other critical user journeys.

    Design

    The order file generation involves the following steps

      • Collect app startup sequence using compiler instrumentation technique
        • Use compiler instrumentation to report every function invocation
        • Run the instrumented binary to collect launch sequence in a (binary) profraw file
      • Generate order file from the profraw files
      • Validate order file
      • Merge multiple order files into one
      • Recompile the app with the merged order file

    Overview

    The order file generation is based on LLVM’s compiler instrumentation process. LLVM has a stage to generate the order file then recompile the source code using the order file.ALT TEXT


    Collect app startup sequence

    The source code is instrumented by passing -forder-file-instrumentation to the compiler. Additionally, the -orderfile-write-mapping flag is also required for the compiler to generate a mapping file. The mapping file is generated during compilation and it is used while processing the profraw file. The mapping file shows the mapping from MD5 hash to function symbol (as shown below).

    # Mapping file MD5 db956436e78dd5fa main MD5 83bff1e88ac48f32 _GLOBAL__sub_I_main.cpp MD5 c943255f95351375 _Z5mergePiiii MD5 d2d2238cf08db816 _Z9mergeSortPiii MD5 11ed18006e729e73 _Z4partPiii MD5 3e897b5ee8bebbd1 _Z9quickSortPiii

    The profile (profraw file) is generated every time the instrumented application is executed. The profile data in the profraw file contains the MD5 hash of the functions executed in chronological order. The profraw file does not have duplicate entries because each function only outputs its MD5 hash on first invocation. A typical run of binary containing the functions listed in the mapping file above can have the following profraw entries.

    # Profraw file 00000000 32 8f c4 8a e8 f1 bf 83 fa d5 8d e7 36 64 95 db |2...........6d..| 00000010 16 b8 8d f0 8c 23 d2 d2 75 13 35 95 5f 25 43 c9 |.....#..u.5._%C.| 00000020 d1 bb be e8 5e 7b 89 3e 00 00 00 00 00 00 00 00 |....^{.>........| 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|

    In order to find the function names corresponding to the MD5 hashes in a profraw file, a corresponding mapping file is used.

    Note: The compiler instrumentation for order files (-forder-file-instrumentation) only works when an optimization flag (01, 02, 03, 0s, 0z) is passed. So, if -O0 (compiler flag typically used for debug builds) is passed, the compiler will not instrument the binary. In principle, one should use the same optimization flag for instrumentation that is used in shipping release binaries.

    The Android NDK repository has scripts that automate the order file generation given a mapping file and an order file.


    Recompiling with Order File

    Once you have an order file, you provide the path of the order file to the linker using the --symbol-ordering-file flag.


    Detailed design

    Creating Order File Build Property

    The Android Open Source Project (AOSP) uses a build system called soong so we can leverage this build system to pass the flags as necessary. The order file build property has four main fields:

      • instrumentation
      • load_order_file
      • order_file_path
      • cflags

    The cflags are meant to add other necessary flags (like mapping flags) during compilation. The load_order_file and order_file_path tells the build system to recompile with the order file rather than set it to the profiling stage. The order files must be in saved in one of two paths:

      • toolchain/pgo-profiles/orderfiles
      • vendor/google_data/pgo_profile/orderfiles

    # Profiling orderfile: { instrumentation: true, load_order_file: false, order_file_path: "", cflags: [ "-mllvm", "-orderfile-write-mapping=<filename>-mapping.txt", ], } #Recompiling with Order File orderfile: { instrumentation: true, load_order_file: true, order_file_path: "<filename>.orderfile", }

    Creating order files

    We provide a python script to create an order file from a mapping file and a profraw file. The script also allows removing a particular symbol or creating an order file until a particular symbol.

    Script Flags:

          • Profile file (--profile-file):
                  • Description: The profile file generated by running a binary compiled with -forder-file-instrumentation
          • Mapping file (--mapping-file):
                  • Description: The mapping file generated during compilation that maps MD5 hashes to symbol names
          • Output file (--output):
                  • Description: The output file name for the order file. Default Name: default.orderfile
          • Deny List (--denylist):
                  • Description: Symbols that you want to exclude from the order file
          • Last symbol (--last-symbol):
                  • Description: The order file will end at the passed last symbol and ignore the symbols after it. If you want an order file only for startup, you should pass the last startup symbol. Last-symbol has priority over leftover so we will output until the last symbol and ignore the leftover flag.
          • Leftover symbols (--leftover):
                  • Description: Some symbols (functions) might not have been executed so they will not appear in the profile file. If you want these symbols in your order file, you can use this flag and it will add them at the end.

    Validating order files

    Once we get an order file for a library or binary, we need to check if it is valid based on a set of criteria. Some order files may not be of good quality so they are better discarded. This can happen due to several reasons like application terminated unexpectedly, the runtime could not write the complete profraw file before exiting, an undesired code-sequence was collected in the profile, etc. To automate this process, we provide a python script that can help developers check for:

      • Partial order that needs to be in the order file
      • Symbols that have to be present in order file
      • Symbols that should not be present in order file
      • Minimum number of symbols to make an order file

    Script Flags:

          • Order file (--order-file):
                  • Description: The order file you are validating on the below criteria.
          • Partial Order (--partial):
                  • Description: A partial order of symbols that must be held in the order file.
          • Allowed Lists (--allowlist):
                  • Description: Symbols that must be present in the order file.
          • Denied Lists (--denylist):
                  • Description: Symbols that should not be in the order file. Denylist flag has priority over allowlist.
          • Minimum Number of Entries (--min):
                  • Description: Minimum number of symbols needed for an order file

    Merging orderfiles

    At a higher level, the order file symbols in a collection of order files approximate a partial order (poset) of function names with order defined by time of execution. Across different runs of an application, the order files might have variations. These variations could be due to OS, device class, build version, user configurations etc. However, the linker can only take one order file to build an application. In order to have one order file that provides the desired benefits, we need to merge these order files into a single order file. The merging algorithm also needs to be efficient so as to not slow down the build time. There are non-linear clustering algorithms that may not scale well for merging large numbers of order files, each having many symbols. We provide an efficient merging algorithm that converges quickly. The algorithm allows for customizable parameters, such that developers can tune the outcome.

    Merging N partial order sets can be done either pessimistically (merging a selection of order files all the way until there is one order file left) or optimistically (merging all of them at once). The pessimistic approach can be inefficient as well as sub-optimal. As a result, it is better to work with all N partial order sets at once. In order to have an efficient implementation it helps to represent all N posets with a weighted directed Graph (V,E) where:

      • V: Elements of partial order sets (symbols) and the number of times it appears in different partial order sets. Note that the frequency of vertices may be greater than the sum of all incoming edges because of invocations from uninstrumented parts of binary, dependency injection etc.
      • E (V1 -> V2): An edge occurs if the element of V2 immediately succeeds V1 in any partial order set with its weight being the number of times this happens.

    For a binary executable, there is one root (e.g., main) vertex, but shared libraries might have many roots based on which functions are called in the binary using them. The graph gets complicated if the application has threads as they frequently result in cycles. To have a topological order, cycles are removed by preferring the highest probability path over others. A Depth-First traversal that selects the highest weighted edge serves the purpose.

    Removing Cycles:

    - Mark back edges during a Depth-First traversal - For each Cycle (c):      - Add the weights of all in-edges of each vertex (v) in the cycle excluding the edges in the cycle      - Remove the cycle edge pointing **to** the vertex with highest sum

    After cycles are removed, the same depth first traversal gives a topological order (the order file) when all the forward edges are removed. Essentially, the algorithm computes a minimum-spanning-tree of maximal weights and traverses the tree in topological order.

    Producing an order:

    printOrderUtil(G, n, order):    - If n was visited:         - return    - Add n to the end of order    - Sort all out edges based on weight    - For every out_edge (n, v):        - printOrderUtil(G, v, order) printOrder(G):    - Get all roots    - order = []    - For each root r:        - printOrderUtil(G, r, order)    - return order

    Example:

    Given the following order files:

      • main -> b -> c -> d
      • main -> a -> c
      • main -> e -> f
      • main -> b
      • main -> b
      • main -> c -> b
    Flow diagram of orderfiles

    The graph to the right is obtained by removing cycles.

      • DFS: main -> b-> c -> b
      • Back edge: c -> b
      • Cycle: b -> c-> b
      • Cycle edges: [b -> c, c -> b]
      • b’s sum of in-edges is 3
      • c’s sum of in-edges is 2
      • This implies b will be traversed from a higher frequency edge, so c -> b is removed
      • Ignore forward edges a->c, main->c
      • The DFS of the acyclic graph on the right will produce an order file main -> b -> c -> d -> a -> e -> f after ignoring the forward edges.

    Collecting order files for Android Apps (Java, Kotlin)

    The order file instrumentation and profile data collection is only enabled for C/C++ applications. As a result, it cannot benefit Java or Kotlin applications. However, Android apps that ship compiled C/C++ libraries can benefit from order file.

    To generate order file for libraries that are used by Java/Kotlin applications, we need to invoke the runtime methods (called as part of order file instrumentation) at the right places. There are three functions that users have to call:

      • __llvm_profile_set_filename(char *f): Set the name of the file where profraw data will be dumped.
      • __llvm_profile_initialize_file: Initialize the file set by __llvm_profile_set_filename
      • __llvm_orderfile_dump: Dumps the profile(order file data) collected while running instrumented binary

    Similarly, the compiler and linker flags should be added to build configurations. We provide template build system files e.g, CMakeLists.txt to compile with the correct flags and add a function to dump the order files when the Java/Kotlin application calls it.

    # CMakeLists.txt set(GENERATE_PROFILES ON) #set(USE_PROFILE "${CMAKE_SOURCE_DIR}/demo.orderfile") add_library(orderfiledemo SHARED orderfile.cpp) target_link_libraries(orderfiledemo log) if(GENERATE_PROFILES) # Generating profiles require any optimization flag aside from -O0. # The mapping file will not generate and the profile instrumentation does not work without an optimization flag. target_compile_options( orderfiledemo PRIVATE -forder-file-instrumentation -O2 -mllvm -orderfile-write-mapping=mapping.txt ) target_link_options( orderfiledemo PRIVATE -forder-file-instrumentation ) target_compile_definitions(orderfiledemo PRIVATE GENERATE_PROFILES) elseif(USE_PROFILE) target_compile_options( orderfiledemo PRIVATE -Wl,--symbol-ordering-file=${USE_PROFILE} -Wl,--no-warn-symbol-ordering ) target_link_options( orderfiledemo PRIVATE -Wl,--symbol-ordering-file=${USE_PROFILE} -Wl,--no-warn-symbol-ordering ) endif()

    We also provide a sample app to dump order files from a Kotlin application. The sample app creates a shared library called “orderfiledemo” and invokes the DumpProfileDataIfNeeded function to dump the order file. This library can be taken out of this sample app and can be repurposed for other applications.

    // Order File Library #if defined(GENERATE_PROFILES) extern "C" int __llvm_profile_set_filename(const char *); extern "C" int __llvm_profile_initialize_file(void); extern "C" int __llvm_orderfile_dump(void); #endif void DumpProfileDataIfNeeded(const char *temp_dir) { #if defined(GENERATE_PROFILES) char profile_location[PATH_MAX] = {}; snprintf(profile_location, sizeof(profile_location), "%s/demo.output", temp_dir); __llvm_profile_set_filename(profile_location); __llvm_profile_initialize_file(); __llvm_orderfile_dump(); __android_log_print(ANDROID_LOG_DEBUG, kLogTag, "Wrote profile data to %s", profile_location); #else __android_log_print(ANDROID_LOG_DEBUG, kLogTag, "Did not write profile data because the app was not " "built for profile generation"); #endif } extern "C" JNIEXPORT void JNICALL Java_com_example_orderfiledemo_MainActivity_runWorkload(JNIEnv *env, jobject /* this */, jstring temp_dir) { DumpProfileDataIfNeeded(env->GetStringUTFChars(temp_dir, 0)); }

    # Kotlin Application class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) runWorkload(applicationContext.cacheDir.toString()) binding.sampleText.text = "Hello, world!" } /** * A native method that is implemented by the 'orderfiledemo' native library, * which is packaged with this application. */ external fun runWorkload(tempDir: String) companion object { // Used to load the 'orderfiledemo' library on application startup. init { System.loadLibrary("orderfiledemo") } } }

    Limitation

    order file generation only works for native binaries. The validation and merging scripts will work for any set of order files.

    References

    External References

    Full-stack development in Project IDX

    Posted by Kaushik Sathupadi, Prakhar Srivastav, and Kristin Bi – Software Engineers; Alex Geboff – Technical Writer

    We launched Project IDX, our experimental, new browser-based development experience, to simplify the chaos of building full-stack apps and streamline the development process from (back)end to (front)end.

    In our experience, most web applications are built with at-least two different layers: a frontend (UI) layer and a backend layer. When you think about the kind of app you’d build in a browser-based developer workspace, you might not immediately jump to full-stack apps with robust, fully functional backends. Developing a backend in a web-based environment can get clunky and costly very quickly. Between different authentication setups for development and production environments, secure communication between backend and frontend, and the complexity of setting up a fully self-contained (hermetic) testing environment, costs and inconveniences can add up.

    We know a lot of you are excited to try IDX yourselves, but in the meantime, we wanted to share this post about full-stack development in Project IDX. We’ll untangle some of the complex situations you might hit as a developer building both your frontend and backend layers in a web-based workspace — developer authentication, frontend-backend communication, and hermetic testing — and how we’ve tried to make it all just a little bit easier. And of course we want to hear from you about what else we should build that would make full-stack development easier for you!


    Streamlined app previews

    First and foremost, we've streamlined the process of enabling your applications frontend communication with its backend services in the VM, making it effortless to preview your full-stack application in the browser.

    IDX workspaces are built on Google Cloud Workstations and securely access connected services through Service Accounts. Each workspace’s unique service account supports seamless, authenticated preview environments for your applications frontend. So, when you use Project IDX, application previews are built directly into your workspace, and you don’t actually have to set up a different authentication path to preview your UI. Currently, IDX only supports web previews, but Android and iOS application previews are coming soon to IDX workspaces near you.

    Additionally, if your setup necessitates communication with the backend API under development in IDX from outside the browser preview, we've established a few mechanisms to temporarily provide access to the ports hosting these API backends.


    Simple front-to-backend communication

    If you’re using a framework that serves both the backend and frontend layers from the same port, you can pass the $PORT flag to use a custom PORT environment variable in your workspace configuration file (powered by Nix and stored directly in your workspace). This is part of the basic setup flow in Project IDX, so you don’t have to do anything particularly special (outside of setting the variable in your config file). Here’s an example Nix-based configuration file:


    { pkgs, ... }: {
    
    # NOTE: This is an excerpt of a complete Nix configuration example.
    
    # Enable previews and customize configuration
    idx.previews = {
      enable = true;
      previews = [
        {
          command = [
            "npm"
            "run"
            "start"
            "--"
            "--port"
            "$PORT"
            "--host"
            "0.0.0.0"
            "--disable-host-check"
          ];
          manager = "web";
          id = "web";
        }
      ];
    };
    

    However, if your backend server is running on a different port from your UI server, you’ll need to implement a different strategy. One method is to have the frontend proxy the backend, as you would with Vite's custom server options.

    Another way to establish communication between ports is to set up your code so the javascript running on your UI can communicate with the backend server using AJAX requests.

    Let’s start with some sample code that includes both a backend and a frontend. Here’s a backend server written in Express.js:


    import express from "express";
    import cors from "cors";
    
    
    const app= express();
    app.use(cors());
    
    app.get("/", (req, res) => {
        res.send("Hello World");
    });
    
    app.listen(6000, () => {
        console.log("Server is running on port 6000");
    })
    

    The bolded line in the sample — app.use(cors()); — sets up the CORS headers. Setup might be different based on the language/framework of your choice, but your backend needs to return these headers whether you’re developing locally or on IDX.

    When you run the server in the IDX terminal, the backend ports show up in the IDX panel. And every port that your server runs on is automatically mapped to a URL you can call.

    Moving text showing the IDX terminal and panel

    Now, let's write some client code to make an AJAX call to this server.


    // This URL is copied from the side panel showing the backend ports view
    const WORKSPACE_URL = "https://6000-monospace-ksat-web-prod-79679-1677177068249.cluster-lknrrkkitbcdsvoir6wqg4mwt6.cloudworkstations.dev/";
    
    async function get(url) {
      const response = await fetch(url, {
        credentials: 'include',
      });
      console.log(response.text());
    }

    // Call the backend
    get(WORKSPACE_URL);

    We’ve also made sure that the fetch() call includes credentials. IDX URLs are authenticated, so we need to include credentials. This way, the AJAX call includes the cookies to authenticate against our servers.

    If you’re using XMLHttpRequest instead of fetch, you can set the “withCredentials” property, like this:


    const xhr = new XMLHttpRequest();
    xhr.open("GET", WORKSPACE_URL, true);
    xhr.withCredentials = true;
    xhr.send(null);
    

    Your code might differ from our samples based on the client library you use to make the AJAX calls. If it does, check the documentation for your specific client library on how to make a credentialed request. Just be sure to make a credentialed request.


    Server-side testing without a login

    In some cases you might want to access your application on Project IDX without logging into your Google account — or from an environment where you can’t log into your Google account. For example, if you want to access an API you're developing in IDX using either Postman or cURL from your personal laptops's command line. You can do this by using a temporary access token generated by Project IDX.

    Once you have a server running in Project IDX, you can bring up the command menu to generate an access token. This access token is a short-lived token that temporarily allows you to access your workstation.

    It’s extremely important to note that this access token provides access to your entire IDX workspace, including but not limited to your application in preview, so you shouldn’t share it with just anyone. We recommend that you only use it for testing.

    Generate access token in Project IDX

    When you run this command from IDX, your access token shows up in a dialog window. Copy the access token and use it to make a cURL request to a service running on your workstation, like this one:


    $ export ACCESS_TOKEN=myaccesstoken
    $ curl -H "Authorization: Bearer $ACCESS_TOKEN" https://6000-monospace-ksat-web-prod-79679-1677177068249.cluster-lknrrkkitbcdsvoir6wqg4mwt6.cloudworkstations.dev/
    Hello world
    

    And now you can run tests from an authenticated server environment!


    Web-based, fully hermetic testing

    As we’ve highlighted, you can test your application’s frontend and backend in a fully self-contained, authenticated, secure environment using IDX. You can also run local emulators in your web-based development environment to test your application’s backend services.

    For example, you can run the Firebase Local Emulator Suite directly from your IDX workspace. To install the emulator suite, you’d run firebase init emulators from the IDX Terminal tab and follow the steps to configure which emulators you want on what ports.

    ALT TEXT

    Once you’ve installed them, you can configure and use them the same way you would in a local development environment from the IDX terminal.


    Next Steps

    As you can see, Project IDX can meet many of your full-stack development needs — from frontend to backend and every emulator in between.

    If you're already using Project IDX, tag us on social with #projectidx to let us know how Project IDX has helped you with your full-stack development. Or to sign up for the waitlist, visit idx.dev.

    Make the passkey endpoints well-known URL part of your passkey implementation

    Posted by Amy Zeppenfeld – Developer Relations Engineer

    Passkeys are leading the charge towards a more secure future without passwords. Passkeys are a new type of cryptographic credential that leverages FIDO2 and WebAuthn to provide an authentication mechanism that is phishing-resistant, user friendly, simple to implement, and more secure than password-based authentication. Most major operating systems and browsers now feature full passkey support. Passkeys are expected to replace passwords as the predominant authentication mechanism in the not-too-distant future, and developers are advised to begin implementing passkey-enabled authentication solutions today.

    As you implement passkeys in your app or web service, take a moment to implement a passkey endpoints well-known URL.

    This is a standardized way to advertise your support for passkeys and optimize user experience. This well-known URL will allow third party services like password managers, passkey providers, and other security tools to direct users to enroll and manage their passkeys for any site that supports them. You can use app-links or deep linking with the passkey-endpoints well-known URL to allow these pages to open directly in your app.

    Password management tool usage has been steadily rising, and we expect most providers will integrate passkey management as well. You can allow third party tools and services to direct your users to your dedicated passkey management page by implementing the passkey-endpoints well-known URL.

    The best part is that in most cases you can implement this feature in two hours or less! All you need to do is host a simple schema on your site. Check out the example below:

    1. For a web service at https://example.com, the well-known URLwould be https://example.com/.well-known/passkey-endpoints
    2. When the URL is queried, the response should use the following schema:
    { "enroll": "https://example.com/account/manage/passkeys/create", "manage": "https://example.com/account/manage/passkeys" }

    Note: You can decide the exact value of the URLs for both enroll and manage based on your website’s own configuration.

    If you have a mobile app, we strongly recommend utilizing deep linking to have these URLs open the corresponding screen for each activity directly in your app to “enroll” or “manage” passkeys. This will keep your users focused and on track to enroll into passkeys.

    And that’s it!

    Further details and examples can be found in the passkey endpoints well-known URL explainer.

    How KAYAK reduced sign in time by 50% and improved security with passkeys

    Posted by Kateryna Semenova, Developer Relations Engineer, Android

    Introduction

    KAYAK is one of the world's leading travel search engines that helps users find the best deals on flights, hotels, and rental cars. In 2023, KAYAK integrated passkeys - a new type of passwordless authentication - into its Android and web apps. As a result, KAYAK reduced the average time it takes their users to sign-up and sign-in by 50%, and also saw a decrease in support tickets.

    This case study explains KAYAK's implementation on Android with Credential Manager API and RxJava. You can use this case study as a model for implementing Credential Manager to improve security and user experience in your own apps.

    If you want a quick summary, check out the companion video on YouTube.


    Problem

    Like most businesses, KAYAK has relied on passwords in the past to authenticate users. Passwords are a liability for both users and businesses alike: they're often weak, reused, guessed, phished, leaked, or hacked.

    “Offering password authentication comes with a lot of effort and risk for the business. Attackers are constantly trying to brute force accounts while not all users understand the need for strong passwords. However, even strong passwords are not fully secure and can still be phished.” – Matthias Keller, Chief Scientist and SVP, Technology at KAYAK

    To make authentication more secure, KAYAK sent "magic links" via email. While helpful from a security standpoint, this extra step introduced more user friction by requiring users to switch to a different app to complete the login process. Additional measures needed to be introduced to mitigate the risk of phishing attacks.


    Solution

    KAYAK's Android app now uses passkeys for a more secure, user-friendly, and faster authentication experience. Passkeys are unique, secure tokens that are stored on the user's device and can be synchronized across multiple devices. Users can sign in to KAYAK with a passkey by simply using their existing device's screen lock, making it simpler and more secure than entering a password.

    “We've added passkeys support to our Android app so that more users can use passkeys instead of passwords. Within that work, we also replaced our old Smartlock API implementation with the Sign in with Google supported by Credential Manager API. Now, users are able to sign up and sign in to KAYAK with passkeys twice as fast as with an email link, which also improves the completion rate" – Matthias Keller, Chief Scientist and SVP, Technology at KAYAK


    Credential Manager API integration

    To integrate passkeys on Android, KAYAK used the Credential Manager API. Credential Manager is a Jetpack library that unifies passkey support starting with Android 9 (API level 28) and support for traditional sign-in methods such as passwords and federated authentication into a single user interface and API.

    Image of Credential Manager's passkey creation screen.
    Figure 1: Credential Manager's passkey creation screens.

    Designing a robust authentication flow for apps is crucial to ensure security and a trustworthy user experience. The following diagram demonstrates how KAYAK integrated passkeys into their registration and authentication flows:

    Flow diagram of KAYAK's registration and authentication processes
    Figure 2:KAYAK's diagram showing their registration and authentication flows.

    At registration time, users are given the opportunity to create a passkey. Once registered, users can sign in using their passkey, Sign in with Google, or password. Since Credential Manager launches the UI automatically, be careful not to introduce unexpected wait times, such as network calls. Always fetch a one-time challenge and other passkeys configuration (such as RP ID) at the beginning of any app session.

    While the KAYAK team is now heavily invested in coroutines, their initial integration used RxJava to integrate with the Credential Manager API. They wrapped Credential Manager calls into RxJava as follows:

    override fun createCredential(request: CreateCredentialRequest, activity: Activity): Single<CreateCredentialResponse> { return Single.create { emitter -> // Triggers credential creation flow credentialManager.createCredentialAsync( request = request, activity = activity, cancellationSignal = null, executor = Executors.newSingleThreadExecutor(), callback = object : CredentialManagerCallback<CreateCredentialResponse, CreateCredentialException> { override fun onResult(result: CreateCredentialResponse) { emitter.onSuccess(result) } override fun onError(e: CreateCredentialException) { emitter.tryOnError(e) } } ) } }

    This example defines a Kotlin function called createCredential() that returns a credential from the user as an RxJava Single of type CreateCredentialResponse. The createCredential() function encapsulates the asynchronous process of credential registration in a reactive programming style using the RxJava Single class.

    For a Kotlin implementation of this process using coroutines, read the Sign in your user with Credential Manager guide.

    New user registration sign-up flow

    This example demonstrates the approach KAYAK used to register a new credential, here Credential Manager was wrapped in Rx primitives.


    webAuthnRetrofitService .getClientParams(username = /** email address **/) .flatMap { response -> // Produce a passkeys request from client params that include a one-time challenge CreatePublicKeyCredentialOption(/** produce JSON from response **/) } .subscribeOn(schedulers.io()) .flatMap { request -> // Call the earlier defined wrapper which calls the Credential Manager UI // to register a new passkey credential credentialManagerRepository .createCredential( request = request, activity = activity ) } .flatMap { // send credential to the authentication server } .observeOn(schedulers.main()) .subscribe( { /** process successful login, update UI etc. **/ }, { /** process error, send to logger **/ } )

    Rx allowed KAYAK to produce more complex pipelines that can involve multiple interactions with Credential Manager.

    Existing user sign-in

    KAYAK used the following steps to launch the sign-in flow. The process launches a bottom sheet UI element, allowing the user to log in using a Google ID and an existing passkey or saved password.

    Image of bottom sheet for passkey authentication
    Figure 3:Bottom sheet for passkey authentication.

    Developers should follow these steps when setting up a sign-in flow:

    1. Since the bottom sheet is launched automatically, be careful not to introduce unexpected wait times in the UI, such as network calls. Always fetch a one-time challenge and other passkeys configuration (such as RP ID) at the beginning of any app session.
    2. When offering Google sign-in via Credential Manager API, your code should initially look for Google accounts that have already been used with the app. To handle this, call the API with the setFilterByAuthorizedAccounts parameter set to true.
    3. If the result returns a list of available credentials, the app shows the bottom sheet authentication UI to the user.
    4. If a NoCredentialException appears, no credentials were found: No Google accounts, no passkeys, and no saved passwords. At this point, your app should call the API again and set setFilterByAuthorizedAccounts to false to initiate the Sign up with Google flow.
    5. Process the credential returned from Credential Manager.
    Single.fromSupplier<GetPublicKeyCredentialOption> { GetPublicKeyCredentialOption(/** Insert challenge and RP ID that was fetched earlier **/) } .flatMap { response -> // Produce a passkeys request GetPublicKeyCredentialOption(response.toGetPublicKeyCredentialOptionRequest()) } .subscribeOn(schedulers.io()) .map { publicKeyCredentialOption -> // Merge passkeys request together with other desired options, // such as Google sign-in and saved passwords. } .flatMap { request -> // Trigger Credential Manager system UI credentialManagerRepository.getCredential( request = request, activity = activity ) } .onErrorResumeNext { throwable -> // When offering Google sign-in, it is recommended to first only look for Google accounts // that have already been used with our app. If there are no such Google accounts, no passkeys, // and no saved passwords, we try looking for any Google sign-in one more time. if (throwable is NoCredentialException) { return@onErrorResumeNext credentialManagerRepository.getCredential( request = GetCredentialRequest(/* Google ID with filterByAuthorizedOnly = false */), activity = activity ) } Single.error(throwable) } .flatMapCompletable { // Step 1: Use Retrofit service to send the credential to the server for validation. Waiting // for the server is handled on a IO thread using subscribeOn(schedulers.io()). // Step 2: Show the result in the UI. This includes changes such as loading the profile // picture, updating to the personalized greeting, making member-only areas active, // hiding the sign-in dialog, etc. The activities of step 2 are executed on the main thread. } .observeOn(schedulers.main()) .subscribe( // Handle errors, e.g. send to log ingestion service. // A subset of exceptions shown to the user can also be helpful, // such as user setup problems. // Check out more info in Troubleshoot common errors at // https://developer.android.com/training/sign-in/passkeys#troubleshoot )


    “Once the Credential Manager API is generally implemented, it is very easy to add other authentication methods. Adding Google One-Tap Sign In was almost zero work after adding passkeys.” – Matthias Keller

    To learn more, follow the guide on how to Integrate Credentials Manager API and how to Integrate Credential Manager with Sign in with Google.


    UX considerations

    Some of the major user experience considerations KAYAK faced when switching to passkeys included whether users should be able to delete passkeys or create more than one passkey.

    Our UX guide for passkeys recommends that you have an option to revoke a passkey, and that you ensure that the user does not create duplicate passkeys for the same username in the same password manager.

    Image of KAYAK's UI for passkey management
    Figure 4:KAYAK's UI for passkey management.

    To prevent registration of multiple credentials for the same account, KAYAK used the excludeCredentials property that lists credentials already registered for the user. The following example demonstrates how to create new credentials on Android without creating duplicates:


    fun WebAuthnClientParamsResponse.toCreateCredentialRequest(): String { val credentialRequest = WebAuthnCreateCredentialRequest( challenge = this.challenge!!.asSafeBase64, relayingParty = this.relayingParty!!, pubKeyCredParams = this.pubKeyCredParams!!, userEntity = WebAuthnUserEntity( id = this.userEntity!!.id.asSafeBase64, name = this.userEntity.name, displayName = this.userEntity.displayName ), authenticatorSelection = WebAuthnAuthenticatorSelection( authenticatorAttachment = "platform", residentKey = "preferred" ), // Setting already existing credentials here prevents // creating multiple passkeys on the same keychain/password manager excludeCredentials = this.allowedCredentials!!.map { it.copy(id = it.id.asSafeBase64) }, ) return GsonBuilder().disableHtmlEscaping().create().toJson(credentialRequest) }

    And this is how KAYAK implemented excludeCredentials functionality for their Web implementation.

    var registrationOptions = { 'publicKey': { 'challenge': self.base64ToArrayBuffer(data.challenge), 'rp': data.rp, 'user': { 'id': new TextEncoder().encode(data.user.id), 'name': data.user.name, 'displayName': data.user.displayName }, 'pubKeyCredParams': data.pubKeyCredParams, 'authenticatorSelection': { 'residentKey': 'required' } } }; if (data.allowCredentials && data.allowCredentials.length > 0) { var excludeCredentials = []; for (var i = 0; i < data.allowCredentials.length; i++) { excludeCredentials.push({ 'id': self.base64ToArrayBuffer(data.allowCredentials[i].id), 'type': data.allowCredentials[i].type }); } registrationOptions.publicKey.excludeCredentials = excludeCredentials; } navigator.credentials.create(registrationOptions);

    Server-side implementation

    The server-side part is an essential component of an authentication solution. KAYAK added passkey capabilities to their existing authentication backend by utilizing WebAuthn4J, an open source Java library.

    KAYAK broke down the server-side process into the following steps:

    1. The client requests parameters needed to create or use a passkey from the server. This includes the challenge, the supported encryption algorithm, the relying party ID, and related items. If the client already has a user email address, the parameters will include the user object for registration, and a list of passkeys if any exist.
    2. The client runs browser or app flows to start passkey registration or sign-in.
    3. The client sends retrieved credential information to the server. This includes client ID, authenticator data, client data, and other related items. This information is needed to create an account or verify a sign-in.

    When KAYAK worked on this project, no third-party products supported passkeys. However, many resources are now available for creating a passkey server, including documentation and library examples.


    Results

    Since integrating passkeys, KAYAK has seen a significant increase in user satisfaction. Users have reported that they find passkeys to be much easier to use than passwords, as they do not require users to remember or type in a long, complex string of characters. KAYAK reduced the average time it takes their users to sign-up and sign-in by 50%, have seen a decrease in support tickets related to forgotten passwords, and have made their system more secure by reducing their exposure to password-based attacks. Thanks to these improvements, ​​KAYAK plans to eliminate password-based authentication in their app by the end of 2023.

    “Passkeys make creating an account lightning fast by removing the need for password creation or navigating to a separate app to get a link or code. As a bonus, implementing the new Credential Manager library also reduced technical debt in our code base by putting passkeys, passwords and Google sign-in all into one new modern UI. Indeed, users are able to sign up and sign in to KAYAK with passkeys twice as fast as with an email link, which also improves the completion rate." – Matthias Keller


    Conclusion

    Passkeys are a new and innovative authentication solution that offers significant benefits over traditional passwords. KAYAK is a great example of how an organization can improve the security and usability of its authentication process by integrating passkeys. If you are looking for a more secure and user-friendly authentication experience, we encourage you to consider using passkeys with Android's Credential Manager API.