Create delightful Wear OS Widgets using sample tile layouts

Posted by Michael Stillwell – Developer Relations Engineer
Golden Tile Templates
Golden Tile Templates

This post is part of Wear OS Spotlight Week. Today, we're focusing on creating engaging Wear OS tiles with new resources and updated design guidance.

Wear OS is all about providing users with the right information at the right time. While a full app is great for immersive experiences, sometimes users just need a quick glance at information or a simple way to take action.

Tiles are fast, predictable surfaces that users can access with a simple swipe from their watch face. They are designed for quick, frequent tasks like checking the weather, starting a timer, or tracking fitness goals.

To streamline your workflow from concept to launch, we're pleased to announce a collection of resources to help you build beautiful and effective tiles using the Material 3 Expressive design system.

These layouts are an evolution of a previous version based on Material 2.5, now updated to Material 3 Expressive to create a modern, premium feel that makes your tile a more cohesive part of the Wear OS.

Material 2.5 and Material 3 Expressive side by side version comparison of goal, media, and ski tiles
Material 2.5 and Material 3 Expressive versions of the "Goal", "Media", and "Ski" tiles

We hope these resources serve as inspiration and a practical starting point, whether you're new to Wear OS development or looking to add a tile to your existing app.

Get started with sample layouts

Tiles are built declaratively using the ProtoLayout library. Material 3 Expressive’s Primary Layout is organized around a slot-based architecture. Running from top to bottom, these slots are:

    • An optional titleSlot for a header.
    • A mandatory mainSlot for your core content.
    • An optional bottomSlot for supplemental actions.

Your app implements a TileService, which returns a layout when requested by the system. This layout is then used to build and render the tile. Learn how to get started with tiles.

As an example, here's the "Goal" layout for visualizing step count. The titleSlot contains the "Steps" text, the mainSlot holds the graphic data card with a progress ring and step data, and the bottomSlot features the "Track" edge-hugging button. (The icon that appears at the top of the tile is specified in your app's manifest is and drawn by the system.)

Daily steps goal tile on a round watch face
Daily steps goal tile

The code for this layout is structured logically around these slots:

fun layout(
    context: Context,
    deviceParameters: DeviceParameters,
    steps: Int,
    goal: Int
) =
    materialScope(
        context = context,
        deviceConfiguration = deviceParameters
    ) {
        val stepsString = NumberFormat.getNumberInstance().format(steps)
        val goalString = NumberFormat.getNumberInstance().format(goal)
        primaryLayout(
            titleSlot = { text("Steps".layoutString) },
            // Adjust margins to create more space when using fully rounded corners
            margins = PrimaryLayoutMargins.MIN_PRIMARY_LAYOUT_MARGIN,
            mainSlot = {
                graphicDataCard(
                    onClick = clickable(),
                    height = expand(),
                    colors = filledTonalCardColors(),
                    title = { text(stepsString.layoutString) },
                    content = { text("of $goalString".layoutString) },
                    horizontalAlignment = LayoutElementBuilders.HORIZONTAL_ALIGN_END,
                    graphic = {
                        constructGraphic(
                            mainContent = {
                                circularProgressIndicator(
                                    staticProgress = 1F * steps / goal,
                                    // On supported devices, animate the arc
                                    dynamicProgress =
                                    DynamicFloat.onCondition(
                                        PlatformEventSources.isLayoutVisible()
                                    )
                                        .use(1F * data.steps / data.goal)
                                        .elseUse(0F)
                                        .animate(
                                            CircularProgressIndicatorDefaults
                                                .recommendedAnimationSpec
                                        ),
                                    startAngleDegrees = 200F,
                                    endAngleDegrees = 520F,
                                )
                            },
                            iconContent = { icon(ICON_ID) },
                        )
                    },
                )
            },
            bottomSlot = {
                textEdgeButton(onClick = clickable()) { text("Track".layoutString) }
            },
        )
    }

With this simple function, you get a great-looking, responsive tile. The ProtoLayout Material3 library handles the heavy lifting, such as setting margins to avoid clipping on round screens and ensuring components adapt smoothly to different display sizes.

Create custom tile layouts

While our layouts cover many common use cases, you'll sometimes need a unique layout. The Material 3 Expressive components provide a flexible foundation for building custom designs.

To translate designs into code, start with the most visually similar layout and modify it. The following sections explain how to modify an existing layout slot by slot.

Customize the title and bottom slots

The titleSlot is often a text() element. To verify that the tap targets of the other elements are interactive, you may wish to hide the title slot on smaller devices. Learn how to develop tiles for different screen sizes.

primaryLayout(
    titleSlot =
        if (isLargeScreen()) {
            { text("$tasksLeft mindful tasks left".layoutString) }
        } else {
            null
        },
    // ...
)

The bottomSlot provides users with a primary action, typically an EdgeButton. You can use a textEdgeButton() for a descriptive action. Alternatively, you can use an icon such as + by using an iconEdgeButton.

Using an icon is a two-step process:

  1. Define the iconEdgeButton in your layout, giving your icon a unique resource ID string:
  2. primaryLayout(
        // ...
        bottomSlot = {
            iconEdgeButton(
                onClick = clickable(),
                modifier = LayoutModifier.contentDescription("Add event"),
                iconContent = { icon("icon_plus_id") }
            )
        }
    )
    

  3. Provide the actual drawable resource in onTileResourcesRequest():
  4. override fun onTileResourcesRequest(
        requestParams: RequestBuilders.ResourcesRequest
    ) =
        Futures.immediateFuture(
            ResourceBuilders.Resources.Builder()
                .setVersion(requestParams.version)
                .addIdToImageMapping(
                    "plus_icon_id",
                    ResourceBuilders.ImageResource.Builder()
                        .setAndroidResourceByResId(
                            ResourceBuilders.AndroidImageResourceByResId.Builder()
                                .setResourceId(R.drawable.outline_add_2_24)
                                .build()
                        )
                        .build()
                )
                .build()
        )
    

See Alarm.kt for a full code sample demonstrating this approach.

Customize the main slot

The mainSlot is where the core content of your tile lives and where the most significant customization occurs. Let's walk through a few examples.

Case study: Workout tile

example of a compact workout tile on a round watch face
A compact workout tile for smaller Wear OS devices.

example of an expanded workout tile on a round watch face
An expanded workout tile providing more information on larger screens.

This tile needs to adapt its layout for different screen sizes. For the smaller layout, three simple iconButton components are a perfect fit. In the larger layout, the central button displays more data (duration, unit, and an icon). Even though it's semantically still a button, in this case the iconDataCard element is a better fit. It's specifically designed to display multiple pieces of data, and we can easily adjust its width and height.

iconDataCard(
    title = { text("30".layoutString, typography = DISPLAY_MEDIUM) },
    content = { text("Mins".layoutString, typography = TITLE_MEDIUM) },
    secondaryIcon = { icon("icon_run_id") },
    shape = shapes.large, // adjust the corner shape
    onClick = clickable(),
    // make element more prominent on larger screens
    width = if (isLargeScreen()) weight(1.5f) else expand(),
    height = expand(),
    // ...
)

See Workout.kt for the full source code.

Case study: Skiing stats tile

example of a custom skiing tile on a round watch face
A custom tile for skiing stats

The design for this tile is built around a pill-shaped element that displays three lines of text, each with unique typography. A textDataCard() is perfect for this, offering slots for a "title" (the metric), "content" (the value), and "secondaryText" (the units). These slots come with default styling that you can override to match your design precisely.

fun MaterialScope.statTextButton(stat: Stat) =
    textDataCard(
        onClick = clickable(),
        width = expand(),
        height = expand(),
        shape = shapes.extraLarge,
        title = {
            text(
                stat.value.layoutString,
                typography =
                    if (isLargeScreen()) {
                        Typography.NUMERAL_SMALL
                    } else {
                        Typography.NUMERAL_EXTRA_SMALL
                    }
            )
        },
        content = {
            text(
                stat.unit.layoutString,
                typography =
                    if (isLargeScreen()) {
                        Typography.TITLE_MEDIUM
                    } else {
                        Typography.TITLE_SMALL
                    }
            )
        },
        secondaryText = {
            text(
                stat.label.layoutString,
                typography =
                    if (isLargeScreen()) {
                        Typography.TITLE_MEDIUM
                    } else {
                        Typography.TITLE_SMALL
                    }
            )
        }
    )

Notice how the typography constants are varied according to the screen size (TITLE_MEDIUM vs. TITLE_SMALL, for example). Given the variety of screen and font sizes on Wear OS, this is a key technique to keep text legible. Instead of trying to manually tweak your layout for every possible combination, focus on adjusting the typography by using different font size constants.

For a consistent look, try to stick to the same "category" of typography constant, such as BODY_MEDIUM on small screens and BODY_LARGE on larger ones. Avoid jumping between different categories, like from LABEL_LARGE to DISPLAY_SMALL, as these constants can vary in more than just size, affecting font weight and other visual properties.

See Ski.kt for the full source code.

Another approach to adapting a layout to different screen sizes is simply to add or remove elements depending on the display size, as demonstrated by the Weather.kt layout. While both versions display the same current conditions, the larger version adds more information to the forecast.

example of a glanceable weatehr tile on a round watch face
A glanceable weather tile for smaller Wear OS screens

example of an expanded weather tile on a round watch face
An enhanced weather tile with forecast details for larger displays.

Customize colors

You might notice that the templates don't specify a color scheme, yet they adapt to the user's chosen theme on Wear OS 6. This is due to dynamic theming, a system feature that automatically generates a color scheme by extracting seed colors from sources like the user's watch face. For tiles, this is the default behavior.

examples of the same weather app featuring three different system-generated color themes
The same Weather tile under three different system-generated color themes

As a developer, this gives you two main options for your tile's appearance:

Option 1 (recommended): Follow dynamic color theming. A dynamic theme is used by default. In this case, you should provide a defaultColorScheme to be used as a fallback if the user disables dynamic theming or if the device doesn't support it. This approach creates a consistent and cohesive feel, allowing your tile to integrate seamlessly with the system.

val myColorScheme =
    ColorScheme(
        primary = ...
        onPrimary = ...
        // 27 more
    )

materialScope(
  defaultColorScheme = myColorScheme
) {
  // If the user selects "no theme" in settings, myColorScheme is used.
  // Otherwise, the system-provided theme is used.
}

Option 2: Use your brand colors. To ensure brand consistency, you can force your tile to always use your custom color scheme by setting allowDynamicTheme to false. In this case, the same colors will always be used, irrespective of the user's selected color theme.

materialScope(
  allowDynamicTheme = false,
  defaultColorScheme = myColorScheme
) {
  // myColorScheme is *always* used.
}

See Theming for more information on the theming system.

Develop and debug

To speed up your development cycle, Wear OS provides several tools to help you debug and test your tiles directly in Android Studio and on the command line.

Dive in and start building

These resources are designed to make building high-quality Wear OS tiles easier and more inspiring. We can't wait to see what you create with them.

Explore the layouts and get started today by checking out the Figma design kit or the code on GitHub.

Ever-present and useful: Building complication data sources for Wear OS

Posted by Garan Jenkin – Developer Relations Engineer
This post is part of Wear OS Spotlight Week. Today, we're focusing on creating engaging experiences across the various surfaces available on the wrist.

Put your app's unique information directly on a user's watch face by building your own complications. These are the small, glanceable details on a watch face, like step count, date, or weather, that are used to convey additional information, beyond simply telling the time.

Watches such as the recently-launched Pixel Watch 4 feature watch faces with as many as 8 complications. These small, powerful display elements are a great way to provide quick, valuable information and keep users connected to your app.

Let’s look at how you can build your own complication data sources, surfacing useful information to the user directly on their watch face, and helping drive engagement with your app.

A round, analog Wear OS watch face showing 8 complications
A watch face showing 8 complications - 4 arc-style complications around the edge of the watch face, and 4 circular complications within the center of the watch face

Key principles of complications

In order to help understand complications, let’s first review some of the key architectural aspects of their design:

    • Apps provide only a complication data source - the watch face takes care of all layout and rendering.
    • Complication data is typed - both complication data sources and watch faces specify which types are supported respectively.
    • Watch faces define slots - these are spaces on the watch face that can host complications.
A flow chart illustrating the flow of requests and ComplicationData between the Wear OS system, watch face, and complication data source
The flow of requests and ComplicationData between the Wear OS system, watch face, and complication data source

What are complications good for?

Complications are great for providing the user with bite-size data during the course of the day. Additionally, complications can provide a great launch point into your full app experience.

Complications Data source types (full list) include SHORT_TEXT and SMALL_IMAGE. Similarly, watch faces declare what types they can render.

For example, if you’re building an app which includes fitness goals, a good choice for a complication data source might be one that provides the GOAL_PROGRESS or RANGED_VALUE data types, to show progress toward that goal.

Conversely, complications are less appropriate for larger amounts of data, such as the contents of a chat message. They’re also not suitable for very frequent updates, such as real-time fitness metrics generated by your app.

Creating a complication data source

Let’s look at creating a complication data source for that fitness goal mentioned above.

First, we create a service that extends SuspendingComplicationDataSourceService:

class MyDataSourceService : SuspendingComplicationDataSourceService() {
    override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationData? {
        // Handle both GOAL_PROGRESS and RANGED_VALUE
        return when (request.complicationType) {
            ComplicationType.GOAL_PROGRESS -> goalProgressComplicationData()
            ComplicationType.RANGED_VALUE -> rangedValueComplicationData()
            else -> NoDataComplicationData()
        }
    }

    // Apps should override this so that watch face previews contain
    // complication data
    override fun getPreviewData(type: ComplicationType) = createPreviewData()
}

To create the actual data to return, we create a ComplicationData object, shown here for GOAL_PROGRESS:

fun goalProgressComplicationData(): ComplicationData {
    val goalProgressText = PlainComplicationText
        .Builder("${goalProgressValue.toInt()} km")
        .build()
    return GoalProgressComplicationData.Builder(
        value = goalProgressValue,
        targetValue = goalTarget,
        contentDescription = goalProgressText
    )
    // Set some additional optional data
    .setText(goalProgressText)
    .setTapAction(tapAction)
    .setMonochromaticImage(...)
    .build()
}

Note: The GoalProgressComplicationData has numerous optional fields in addition to the mandatory ones. You should try to populate as many of these as you can.

Finally, add the data source to the manifest:

<service
    android:name=".WorkoutStatusDataSourceService"
    android:exported="true"
    android:directBootAware="true"
    android:label="@string/status_complication_label"
    android:permission="com.google.android.wearable.permission.BIND_COMPLICATION_PROVIDER">
    <intent-filter>
        <action android:name="android.support.wearable.complications.ACTION_COMPLICATION_UPDATE_REQUEST" />
    </intent-filter>

    <!--
      Supported data types. Note that the preference order of the watch face,
      not the complication data source, decides which type will be chosen.
    -->
    <meta-data
        android:name="android.support.wearable.complications.SUPPORTED_TYPES"
        android:value="GOAL_PROGRESS,RANGED_VALUE" />
    <meta-data
        android:name="android.support.wearable.complications.UPDATE_PERIOD_SECONDS"
        android:value="300" />
</service>
Note: The use of the directBootAware attribute on the service lets the complication service run before the user has unlocked the device on boot.

Choosing your update model

Complications support both a push and a pull-style update mechanism. In the example above, UPDATE_PERIOD_SECONDS is set such that the data is refreshed every 5 minutes. Wear OS will check the updated value of the complication data source with that frequency.

This works well for a scenario such as a weather complication, but in other scenarios, it may make more sense for the updates to be driven by the app. To achieve this, you can:

  1. Set UPDATE_PERIOD_SECONDS to 0 to indicate that the app will drive the update process.
  2. Using ComplicationDataSourceUpdateRequester in your app code to signal to the Wear OS system that an update should be requested, for example in a WorkManager job, or in WearableListenerService.

Leveraging platform bindings for high-frequency data

Particularly for health-related complications, we can take advantage of platform data sources, to improve our goal progress complication. We can use these data sources with dynamic expressions to create complication content which is dynamically re-evaluated every second while the watch face is in interactive mode (that is, when it’s not in system ambient / always-on mode).

Let’s update the complication so that instead of just showing the distance, it shows a celebratory message when the target is reached. First we create a dynamic string as follows:

val distanceKm = PlatformHealthSources.dailyDistanceMeters().div(1000f)
val formatter = DynamicBuilders.DynamicFloat.FloatFormatter.Builder()
    .setMaxFractionDigits(2)
    .setMinFractionDigits(0)
    .build()
val goalProgressText = DynamicBuilders.DynamicString
    .onCondition(distanceKm.lt(distanceKmTarget))
    .use(
        distanceKm
            .format(formatter)
            .concat(DynamicBuilders.DynamicString.constant(" km"))
    )
    .elseUse(
        DynamicBuilders.DynamicString.constant("Success!")
    )

Then we include this text, and the dynamic value distanceKm, with the dynamic version of the complication builder.

In this way, the distance is updated every second, with no need for further requests to the data source. This means UPDATE_PERIOD_SECONDS can be set to a large value, saving battery, and the celebratory text is immediately shown the moment they pass their target!

Configuring complications

For some data sources, it is useful to let the user configure what data should be shown. In the fitness goal example, consider that the user might have weekly, monthly, and yearly goals.

Adding a configuration activity allows them to select which goal should be shown by the complication. To do this, add the PROVIDER_CONFIG_ACTION metadata to your service definition, and implement an activity with a filter for this intent, for example:

<service android:name=".MyGoalDataSourceService" ...>
  <!-- ... -->

  <meta-data
      android:name="android.support.wearable.complications.PROVIDER_CONFIG_ACTION"
      android:value="com.myapp.MY_GOAL_CONFIG" />
</service>

<activity android:name=".MyGoalConfigurationActivity" ...>
  <intent-filter>
    <action android:name="com.myapp.MY_GOAL_CONFIG" />
    <category android:name="android.support.wearable.complications.category.PROVIDER_CONFIG" />
    <category android:name="android.intent.category.DEFAULT" />
  </intent-filter>
</activity>

In the activity itself, the details of the complication being configured can be extracted from the intent:

// Keys defined on ComplicationDataSourceService
// (-1 assigned when the ID or type was not available)
val id = intent.getIntExtra(EXTRA_CONFIG_COMPLICATION_ID, -1)
val type = intent.getIntExtra(EXTRA_CONFIG_COMPLICATION_TYPE, -1)
val source = intent.getStringExtra(EXTRA_CONFIG_DATA_SOURCE_COMPONENT)

To indicate a successful configuration, the activity should set the result when exiting:

setResult(Activity.RESULT_OK) // Or RESULT_CANCELED to cancel configuration
finish()

The ID is the same ID passed in ComplicationRequest to the complication data source service. The Activity should write any configuration to a data store, using the ID as a key, and the service can retrieve the appropriate configuration to determine what data to return in response to each onComplicationRequest().

Working efficiently with time and events

In the example above, UPDATE_PERIOD_SECONDS is set at 5 minutes - this is the smallest value that can be set for the update period. Ideally this value should be set as large as is acceptable for the use case: This reduces requests and improves battery life.

Consider these examples:

    • A known list of events - For example a calendar. In this case, use SuspendingTimelineComplicationDataSourceService.
    • This allows you to provide the series of events in advance, with no need for the watch face to request updates. The calendar data source would only need to push updates if a change is made, such as another event being scheduled for the day, offering timeliness and efficiency.

      ComplicationDataTimeline requires a defaultComplicationData as well as the list of entries: This is used in the case where none of the timeline entries are valid for the current time. For example, for a calendar it could contain the text “No event” where the user has nothing booked. Where there are overlapping entries, the entry with the shortest interval is chosen.

override suspend fun onComplicationRequest(request: ComplicationRequest): ComplicationDataTimeline? {
    return ComplicationDataTimeline(
        // The default for when there is no event in the calendar
        defaultComplicationData = noEventComplicationData,
        // A list of calendar entries
        timelineEntries = listOf(
            TimelineEntry(
                validity = TimeInterval(event1.start, event1.end),
                complicationData = event1.complicationData
            ),
            TimelineEntry(
                validity = TimeInterval(event2.start, event2.end),
                complicationData = event2.complicationData
            )
        )
    )
}

    • Working with time or timers - If your complication data contains time or a timer, such as a countdown to a particular event, use built-in classes such as TimeDifferenceComplicationText and TimeFormatComplicationText - this keeps the data up-to-date while avoiding regular requests to the data source service.
    • For example, to create a countdown to the New Year:

TimeDifferenceComplicationText.Builder(
    TimeDifferenceStyle.SHORT_SINGLE_UNIT,
    CountDownTimeReference(newYearInstant)
)
.setDisplayAsNow(true)
.build()

    • Data that should be shown at a specific time and/or duration - use setValidTimeRange() to control when complication data should be shown, again avoiding repeated updates.
    • This can be useful in the case where it is not possible to use a timeline but where data can become stale, allowing you to control the visibility of this data.

Working with activation and deactivation

It can be very useful to track whether your complication is currently in use on the active watch face or not. This can help with:

  1. Avoiding unnecessary work - for example, if a weather complication has not been set in the active watch face, then there is no need to enable a WorkManager job to periodically fetch weather updates, saving battery and network usage.
  2. Aiding discovery - if onComplicationActivated has never been called, then the user has never used your complication on a watch face.
  3. This can be a useful signal to provide an educational moment in your phone or Wear OS app, drawing attention to this feature, and sharing potential benefits with the user that they may not be aware of.

To facilitate these use cases, override the appropriate methods in your complication service:

class MyDataSourceService() : SuspendingComplicationDataSourceService() {
    override fun onComplicationActivated(complicationInstanceId: Int, type: ComplicationType) {
        super.onComplicationActivated(complicationInstanceId, type)

        // Keep track of which complication has been enabled, and
        // start any necessary work such as registering periodic
        // WorkManager jobs
    }
    
    override fun onComplicationDeactivated(complicationInstanceId: Int) {
        super.onComplicationDeactivated(complicationInstanceId)

        // Complication instance has been disabled, so remove all
        // registered work
    }

Some additional points to consider when implementing your data sources:

    • Support multiple types to maximize usefulness and compatibility - Watch faces will support some complication data types, but likely not all of them.
    • Adding support to your data source for multiple types makes it most useful to the user. In the above example, we implemented both RANGED_VALUE and GOAL_PROGRESS, as both can be used to represent progress-type data.

      Similarly, if you were to implement a calendar complication, you could use both SHORT_TEXT and LONG_TEXT to maximize compatibility with the available slots on the watch face.

    • Use different data sources for different user journeys - Your app is not limited to providing one complication data source. You should support more than one if you have different use cases to cater for. For example, your health and fitness app might have a complication to provide your progress towards your goals, but also a separate complication to show sleep stats.
    • Avoid heavy work in onComplicationRequest() - For example,if the progress toward a fitness goal involves intensive processing of a large number of workouts, do this elsewhere. The request to the complication data source should ideally just return the value with minimal computation.
    • Avoid your service having extensive dependencies on other app components - When in use, your data source service will be started when the Wear OS device starts up, and at other times during the day. You should avoid the service needing too many other components from within your app to be started in order to run, to maintain good system performance.
    • Consider backup and restore - If the complication is configurable, it might make sense to restore these settings - learn how to implement backup and restore for complication data sources.
    • Think about the discovery journey - Your complications will be available as an option on the user’s watch face when your app is installed on the watch. Consider how you can promote and educate the user on this functionality, both in your phone app and your Wear OS app, and leverage methods such as onComplicationActivated() to inform this process.
    • Resources for creating complications

      Complications are a great way to elevate your app experience for users, and to differentiate your app from others.

      Check out these resources for more information on creating complication data sources. We look forward to seeing what you can do.

      Happy Coding!

Adding image support to Gemini in the side panel of Google Drive

What’s changing 

Users have been able to use Gemini in the side panel of Google Drive to summarize one or multiple documents, interact with PDFs, have focused conversations about a specific Drive folder, create files and folders, and more. However, until now, this functionality was text-based only and did not account for images saved in your Drive. 

Similar to the recent video announcement, we’re happy to announce that Gemini can now answer questions or give you summaries about images in Drive. Here are some examples of what you can ask Gemini to: 

  • Summarize this image
  • Extract text from this image
  • Extract information from this receipt/invoice into a table
  • Generate alternate text for this image
  • Write a story about this image

Gemini is extracting information from this receipt into a table 

Additional details 

This feature is currently available in English only and works best for:
  • Scanned documents like contracts, receipts, invoices, etc. 
  • Text-heavy images 

Getting started 


Rollout pace 


Availability 

Available for Google Workspace: 
  • Business Standard and Plus 
  • Enterprise Standard and Plus 
  • Customers with the Gemini Education or Gemini Education Premium add-on 
  • Google One AI Premium 
Anyone who previously purchased these add-ons will also receive this feature: 
  • Gemini Business* 
  • Gemini Enterprise* 
*As of January 15, 2025, we’re no longer offering the Gemini Business and Gemini Enterprise add-ons for sale. Please refer to this announcement for more details. 

Resources 





Context-Aware Access policies can now be applied to all internal and third-party apps using OpenID Connect

What’s changing 

Admins can now apply Context-Aware Access (CAA) policies to apps which use OpenID Connect (OIDC), which are a subset of OAuth apps that are authenticated using Google sign-in. Admins can use a single setting to apply CAA policies to all OIDC apps by default. We are not providing per app access control for individual apps at this moment. The new OIDC setting can also be applied in monitor mode for admins to gauge potential end user impact before applying in active mode. 

CAA creates granular access control security policies for apps based on attributes, such as user identity, location, device security status, and IP address, and they can be applied to users on personal and managed devices. Expanding CAA to encompass OIDC apps means admins can ensure their users are able to access or are blocked from accessing these apps according to the broader security parameters of their organizations. 

Admins can configure CAA policies for OIDC apps in the Admin console under Security > Context-Aware Access > General settings 

Getting started 

  • Admins: CAA for OIDC apps can be configured at the OU level. Visit the Help Center to learn more about context-aware access, creating context-aware access levels, and assigning access levels to third-party apps. 
  • End users: If enabled by your admin, you can access certain apps when authenticating using your Google sign-in. Or you may see a message letting you know that you cannot use Google sign-in to authenticate with certain apps or you may see remediation messages which will provide some options on how to unblock apps. 

Rollout pace 


Availability 

Available for Google Workspace: 
  • Frontline Standard and Plus 
  • Enterprise Standard and Plus 
  • Education Standard and Plus 
  • Enterprise Essentials Plus 
  • Also available for Cloud Identity Premium 

Resources 

A new layer of security for certified Android devices

Posted by Suzanne Frey – VP, Product, Trust & Growth for Android

You shouldn’t have to choose between open and secure. By engineering security into the core part of the OS, Android has proven that you can have both, and we continue taking new steps in that direction.

As new threats emerge, we’ve continued to evolve our defenses. Following recent attacks, including those targeting people's financial data on their phones, we've worked to increase developer accountability to prevent abuse. We’ve seen how malicious actors hide behind anonymity to harm users by impersonating developers and using their brand image to create convincing fake apps. The scale of this threat is significant: our recent analysis found over 50 times more malware from internet-sideloaded sources than on apps available through Google Play.

To better protect users from repeat bad actors spreading malware and scams, we're adding another layer of security to make installing apps safer for everyone: developer verification.

Starting next year, Android will require all apps to be registered by verified developers in order to be installed by users on certified Android devices. This creates crucial accountability, making it much harder for malicious actors to quickly distribute another harmful app after we take the first one down. Think of it like an ID check at the airport, which confirms a traveler's identity but is separate from the security screening of their bags; we will be confirming who the developer is, not reviewing the content of their app or where it came from. This change will start in a few select countries specifically impacted by these forms of fraudulent app scams, often from repeat perpetrators.

Since we implemented verification requirements on Google Play in 2023, we have seen firsthand how helpful developer identification is in stopping bad actors from exploiting anonymity to distribute malware, commit financial fraud, and steal sensitive data. Bringing a similar process to Android more broadly will provide a consistent, common sense baseline of developer accountability across the ecosystem.

In early discussions about this initiative, we've been encouraged by the supportive initial feedback we've received. In Brazil, the Brazilian Federation of Banks (FEBRABAN) sees it as a “significant advancement in protecting users and encouraging accountability.” This support extends to governments as well, with Indonesia's Ministry of Communications and Digital Affairs praising it for providing a “balanced approach” that protects users while keeping Android open. Similarly, Thailand’s Ministry of Digital Economy and Society sees it as a “positive and proactive measure” that aligns with their national digital safety policies. And partners like the Developer’s Alliance have called this a “critical step” for ensuring “trust, accountability, and security” across the entire ecosystem.

To make this process as streamlined as possible, we are building a new Android Developer Console just for developers who only distribute outside of Google Play, so they can easily complete their verification; get an early look at how it works. A note for student and hobbyist developers: we know your needs are different from commercial developers, so we’re creating a separate type of Android Developer Console account for you.

If you distribute apps on Google Play, you’ve likely already met these verification requirements through the existing Play Console process. You can find more information about how these requirements apply to you in our guides.

To be clear, developers will have the same freedom to distribute their apps directly to users through sideloading or to use any app store they prefer. We believe this is how an open system should work—by preserving choice while enhancing security for everyone. Android continues to show that with the right design and security principles, open and secure can go hand in hand. For more details on the specific requirements, visit our website. We'll share more information in the coming months.

Timeline and how to prepare

To help you get ready, we encourage all developers who distribute apps on certified Android devices to sign up for early access. This is the best way to prepare and stay informed.

Early participants will also get:

    • An invitation to an exclusive community discussion forum.
    • Priority support for these new requirements.
    • The chance to provide feedback and help us shape the experience.

Sign up for early access now

Here is the timeline to help you plan:

    • October 2025: Early access begins. Invitations will be sent out gradually.
    • March 2026: Verification opens for all developers.
    • September 2026: These requirements go into effect in Brazil, Indonesia, Singapore, and Thailand. At this point, any app installed on a certified Android device in these regions must be registered by a verified developer.
    • 2027 and beyond: We will continue to roll out these requirements globally.

Welcome to Wear OS Spotlight Week

Posted by Chiara Chiappini – Android Developer Relations Engineer, and Kevin Hufnagle - Android Technical Writer

Wear OS is rapidly expanding its presence in the market, presenting a unique and significant opportunity for developers. With a growing number of users wearing and interacting with their smartwatches daily, building for Wear OS allows you to reach an even broader audience of Android users and boost your app's engagement more than ever before. The introduction of new hardware like the Pixel Watch 4 is a key driver of this momentum, enabling developers to bring premium Wear OS experiences to this expanding user base.

This week, we're putting a special focus on Wear OS: Welcome to Wear OS Spotlight Week!

Throughout the week, we'll dive into the different Wear OS surfaces where you can develop on-the-watch experiences. This blog post will be updated throughout the week with links to new announcements and resources, so check back here daily for updates.


Day 1: Material 3 Expressive on Wear OS

Monday, August 25, 2025

Learn how you can build beautiful and tailored Wear OS apps and tiles using the Material 3 Expressive Design language and Jetpack libraries for Wear OS.

To further explore the key principles and main features of Wear OS’s new design system, consult our updated design guidance on the Android developer documentation website.

Day 2: Build apps, tiles, and complication on Wear OS

Tuesday, August 26, 2025

Discover how to build engaging experiences across Wear OS’s surfaces, including apps, tiles, complications, and notifications. Learn how to create quick, glanceable content using familiar tools like Jetpack Compose and the ProtoLayout library, all while leveraging the beautiful new Material 3 Expressive design system.

Next, understand how to build beautiful and effective Wear OS tiles using the Material 3 Expressive design system and a new collection of resources.

Dive deep into building complications that support data sources which helps you show useful information to the user directly on the watch face, and can drive engagement with your app.

Lastly, find out how Todoist has applied the latest updates on Wear OS including new integrations with Material 3 Expressive and Credential Manager. Improvements like these have helped Todoist become the world’s top task and time management app.

Day 3: Watch faces

Wednesday, August 27, 2025

Explore the wonderful world of watch faces! The Watch Face Push API for Wear OS 6 is here to unlock a world of dynamic watch faces. This powerful tool lets you create your own marketplace experience for the watch faces you create. Dive in and explore how you can promote your engaging watch faces today! 🎨.

Next, discover how Amoledwatchfaces, a leading creator, successfully migrated to the Watch Face Format for their 190+ watch faces. This switch led to faster development, improved battery life, and more customizable designs.

Lastly, learn about Watch Face Designer, a new Figma plugin that’s available for watch face designers and developers to create watch faces with greater ease.

Day 4: Credential Manager on Wear OS

Thursday, August 28, 2025

Discover how to streamline authentication on your Wear OS app by using Credential Manager.

We’ve got some great new resources to help you learn how everything works, and to help you get started crafting your own implementation:

Day 5: #AskAndroid

Friday, August 29, 2025

Join us for a live Q&A on Wear OS! Ask your questions at Android Developers on X and Android by Google at Linkedin using the tag #AskAndroid.

Explore and create your own experiences with Wear OS

Explore the latest updates for Wear OS, and delve into the wealth of resources shared during the week. We’re excited to see the results of your explorations with building apps, tiles and watch faces for Wear OS.

Happy coding!