静态卡片

您可以使用简单的 REST API 插入、更新、读取和删除静态卡片。此外,您还可以将对象(例如位置或媒体)附加到静态卡片。

运作方式

静态卡片默认位于 Google 眼镜时钟的右侧,并会在传送时显示与用户相关的信息。不过,它们不需要像动态卡片那样立即引起用户注意,用户可以选择在方便时阅读或根据卡片中的说明采取行动。

当 Glassware 将静态卡片插入时间轴时,Glass 可能会播放通知提示音来提醒用户。所有之前的静态卡片也会向右移,并会在 7 天后或有 200 张更新的卡片后从时间轴中消失。

何时使用此类附加信息

静态卡片非常适合在发生重要事件时向用户发送定期通知。例如,新闻提送服务会在有热门新闻发生时发送相关报道。Mirror API 静态卡片还可以通过 OPEN_URI 菜单项启动实时卡片沉浸式体验。这样,您就可以创建混合互动,将静态卡片用作通知,并使用实时卡片或沉浸式体验来打造更具互动性的体验。

如需查看时间轴项的可能操作的完整列表,请参阅参考文档

插入静态卡片

如需插入静态卡片(时间轴项),请将时间轴项的 JSON 表示法 POST 到 REST 端点。

时间轴项中的大多数字段都是可选的。形式最简单的时间轴项仅包含一条简短的文本消息,如以下示例所示:

原始 HTTP

POST /mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {auth token}
Content-Type: application/json
Content-Length: 26

{ "text": "Hello world" }

Java

TimelineItem timelineItem = new TimelineItem();
timelineItem.setText("Hello world");
service.timeline().insert(timelineItem).execute();

Python

timeline_item = {'text': 'Hello world'}
service.timeline().insert(body=timeline_item).execute()

成功后,您会收到一个 201 Created 响应代码,其中包含所创建内容的完整副本。对于上例,成功响应可能如下所示:

原始 HTTP

HTTP/1.1 201 Created
Date: Tue, 25 Sep 2012 23:30:11 GMT
Content-Type: application/json
Content-Length: 303

{
 "kind": "glass#timelineItem",
 "id": "1234567890",
 "selfLink": "https://www.googleapis.com/mirror/v1/timeline/1234567890",
 "created": "2012-09-25T23:28:43.192Z",
 "updated": "2012-09-25T23:28:43.192Z",
 "etag": "\"G5BI0RWvj-0jWdBrdWrPZV7xPKw/t25selcGS3uDEVT6FB09hAG-QQ\"",
 "text": "Hello world"
}

插入的项目将显示在用户的时间轴中,如下所示:

插入带有附件的时间轴项

一张图片胜过千言万语,而时间轴项中能容纳的内容远远不止千言万语。为此,您还可以向时间轴项附加图片和视频。以下示例展示了如何插入包含照片附件的时间轴项:

原始 HTTP

POST /upload/mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {auth token}
Content-Type: multipart/related; boundary="mymultipartboundary"
Content-Length: {length}

--mymultipartboundary
Content-Type: application/json; charset=UTF-8

{ "text": "A solar eclipse of Saturn. Earth is also in this photo. Can you find it?" }
--mymultipartboundary
Content-Type: image/jpeg
Content-Transfer-Encoding: binary

[binary image data]
--mymultipartboundary--

Java

TimelineItem timelineItem = new TimelineItem();
timelineItem.setText("Hello world");
InputStreamContent mediaContent = new InputStreamContent(contentType, attachment);
service.timeline().insert(timelineItem, mediaContent).execute();

Python

timeline_item = {'text': 'Hello world'}
media_body = MediaIoBaseUpload(
    io.BytesIO(attachment), mimetype=content_type, resumable=True)
service.timeline().insert(body=timeline_item, media_body=media_body).execute()

在 Google Glass 上,带有附加图片的时间轴项如下所示:

附加视频

如果您要将视频文件附加到时间轴项,我们建议您流式传输视频,而不是一次上传整个载荷。Google Mirror API 支持使用 HTTP Live Streaming、渐进式下载和实时流协议 (RTSP) 进行流式传输。RTSP 通常会被防火墙屏蔽,因此请尽可能使用其他选项。

如需流式传输视频,请使用 PLAY_VIDEO 内置菜单项,并将视频的网址指定为菜单项的 payload。如需了解详情,请参阅添加内置菜单项支持的媒体格式

分页

如果时间轴项无法在一张时间轴卡片上显示,但应与同一张卡片相关联,您可以对其进行分页。分页项都共用相同的 timeline.id,因此具有一组相同的菜单项。当用户点按分页时间轴项时,系统会显示了解详情菜单项。

Glass 会自动对显示 text 的时间轴项进行分页。如需让 Glass 自动分页 html,请使用 article 标记,并将其类属性设置为 auto-paginate,如以下示例所示:

<article class="auto-paginate">
 <h3>Very long list</h3>
 <ul>
   <li>First item</li>
   <li>Second item</li>
   <li>Third item</li>
   <li>Fourth item</li>
   <li>Fifth item</li>
   <li>Sixth item</li>
   <li>...</li>
 </ul>
<article>

如需手动分页,请为您要在每张卡片上显示的内容使用 article 标记。Glass 会在单独的子时间轴卡片中显示每个 article 标记的内容。例如,您可以使用以下 HTML 创建分页时间轴项:

<article>
 <section>
   <p>First page</p>
 </section>
</article>

<article>
 <section>
   <p>Second page</p>
 </section>
</article>

<article>
 <section>
   <p>Third page</p>
 </section>
</article>

默认情况下,分页时间轴项的第一张卡片会显示为封面卡片,并会在用户选择了解详情菜单项时再次显示。如需阻止第一个卡片在点按了解详情后再次显示,您可以为第一个 <article> 标记指定 cover-only CSS 类:

<article class="cover-only">
...

cover-only 类还支持自动分页的时间轴项:

<article class="auto-paginate cover-only">
...

捆绑

借助捆绑功能,您可以将相关但不同的项分组在一起,例如电子邮件会话中的各个邮件。文件包有一个主封面卡片,用户点按该卡片即可显示包含文件包中其他卡片的子时间轴。文件包与普通时间轴卡片的区别在于,文件包封面卡片的右上角有一个折角。

如需将时间轴项打包,请为其创建具有相同 bundleId 值的项。最近添加的项是套装的封面卡片。

以下图片显示了右上角带有角折的套装封面卡片,以及位于其下方的两个套装卡片。

阅读时间轴项

您的服务可以访问其创建的所有时间轴项,以及与其共享的所有时间轴项。您可以按照以下步骤列出对您的服务可见的时间轴项。

原始 HTTP

GET /mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {auth token}

Java

TimelineItem timelineItem = new TimelineItem();
service.timeline().list().execute();

Python

service.timeline().list().execute()

您可以使用其他 REST 操作来获取更新删除时间轴项。

访问附件

您可以通过名为 attachments 的数组属性访问时间轴项的附件。然后,您可以通过附件的 contentUrl 属性或附件端点获取附件的二进制数据。

原始 HTTP

GET /mirror/v1/timeline/{itemId}/attachments/{attachmentId} HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {auth token}

Java

TimelineItem item = service.timeline().get(itemId).execute();
String attachmentId = item.getAttachments().get(0).getId();
service.attachments().get(itemId, attachmentId).executeAsInputStream();

创建菜单项

菜单项可让用户请求与时间轴卡片相关的操作,分为两种类型:内置菜单项和自定义菜单项。

借助内置菜单项,您可以使用 Google 眼镜提供的特殊功能,例如大声朗读时间轴卡片、导航到某个位置、分享图片或回复消息:

借助自定义菜单项,您的应用可以显示特定于您的 Glassware 的行为,您还可以提供与您的品牌相符的菜单项图标。

添加内置菜单项

您可以在插入时间轴项时填充 menuItems array,以便向时间轴项添加内置菜单项。如需使用内置菜单项,您只需填充每个 menuItemaction

原始 HTTP

HTTP/1.1 201 Created
Date: Tue, 25 Sep 2012 23:30:11 GMT
Content-Type: application/json
Content-Length: 303

{
  "text": "Hello world",
  "menuItems": [
    {
      "action": "REPLY"
    }
  ]
}

定义自定义菜单项

如果内置菜单项不适合您,您可以在插入或更新时间轴项时执行以下操作,以便使用自己的操作创建自定义菜单项:

  • menuItem.action 指定 CUSTOM
  • 指定 menuItem.id。当用户点按自定义菜单项时,您的 Glassware 会收到一个已填充 menuItem.id 的通知。这样,您就可以确定通知的来源。
  • 指定 menuItem.values 以添加在 Glass 上显示的 iconUrldisplayName。为 iconUrl 指向一个 50 x 50 像素的 PNG 图片,该图片应为白色且具有透明背景。
  • 指定 displayTime。如果您未指定 displayTime,则每当用户点按自定义菜单项时,时间轴项都会移至时间轴前面。

原始 HTTP

HTTP/1.1 201 Created
Date: Tue, 25 Sep 2012 23:30:11 GMT
Content-Type: application/json
Content-Length: 303

{
  "text": "Hello world",
  "displayTime": "2013-08-08T22:47:31-07:00",
  "menuItems": [
    {
      "action": "CUSTOM",
      "id": "complete"
      "values": [{
        "displayName": "Complete",
        "iconUrl": "http://example.com/icons/complete.png"
      }]
    }
  ]
}

允许用户固定您的时间轴卡片

您可以创建一个菜单项,让用户固定时间轴卡片,以便在主时钟卡片左侧永久显示时间轴卡片。用户也可以使用相同的菜单项取消固定卡片。

固定菜单项是一种内置菜单项,因此您只需为 menuItem 提供 TOGGLE_PINNED action 即可。

原始 HTTP

HTTP/1.1 201 Created
Date: Tue, 25 Sep 2012 23:30:11 GMT
Content-Type: application/json
Content-Length: 303

{
  "text": "You can pin or unpin this card.",
 "menuItems": [
    {
      "action": "TOGGLE_PINNED"
    }
  ...
 ]
}

订阅

借助 Mirror API,您可以订阅通知,这些通知会在用户对时间轴项执行特定操作或用户位置信息更新时发送。订阅通知时,您需要提供用于处理通知的回调网址。

接收通知

Mirror API 中的通知会以 POST 请求的形式发送到已订阅的端点,其中包含 JSON 请求正文。

原始 HTTP

{
  "collection": "timeline",
  "itemId": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation": "UPDATE",
  "userToken": "harold_penguin",
  "verifyToken": "random_hash_to_verify_referer",
  "userActions": [
    {
      "type": "<TYPE>",
      "payload": "<PAYLOAD>"
    }
  ]
}

Java

import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.mirror.model.Notification;

import java.io.IOException;
import java.io.InputStream;
// ...

public class MyClass {
  // ...

  /**
    * Parse a request body into a Notification object.
    *
    * @param requestBody The notification payload sent by the Mirror API.
    * @return Parsed notification payload if successful, {@code null} otherwise.
    */
  static Notification parseNotification(InputStream requestBody) {
    try {
      JsonFactory jsonFactory = new JacksonFactory();

      return jsonFactory.fromInputStream(requetBody, Notification.class);
    } catch (IOException e) {
      System.out.println("An error occurred: " + e);
      return null;
    }
  }

  // ...
}

Python

import json

def parse_notification(request_body):
  """Parse a request body into a notification dict.

  Params:
    request_body: The notification payload sent by the Mirror API as a string.
  Returns:
    Dict representing the notification payload.
  """
  return json.load(request_body)

如果未发生错误,您的服务必须使用 200 OK HTTP 状态代码响应 API。如果您的服务响应时返回错误代码,Mirror API 可能会尝试将通知重新发送到您的服务。

通知类型

Mirror API 会针对不同的事件发送不同的通知载荷。

回复

用户使用内置的 REPLY 菜单项回复了您的时间轴项:

{
  "collection": "timeline",
  "itemId": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation": "INSERT",
  "userToken": "harold_penguin",
  "verifyToken": "random_hash_to_verify_referer",
  "userActions": [
    {
      "type": "REPLY"
    }
  ]
}

itemId 属性设置为包含以下内容的项的 ID

  • inReplyTo 属性设置为其回复的时间轴项的 ID
  • text 属性设置为文本转写。
  • recipients 属性设置为其回复的时间轴项的 creator(如果存在)。

示例:

{
  "kind": "glass#timelineItem",
  "id": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "inReplyTo": "3236e5b0-b282-4e00-9d7b-6b80e2f47f3d",
  "text": "This is a text reply",
  "recipients": [
    {
      "id": "CREATOR_ID",
      "displayName": "CREATOR_DISPLAY_NAME",
      "imageUrls": [
        "CREATOR_IMAGE_URL"
      ]
    }
  ]
}

删除

用户删除了时间轴项:

{
  "collection": "timeline",
  "itemId": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation": "DELETE",
  "userToken": "harold_penguin",
  "verifyToken": "random_hash_to_verify_referer",
  "userActions": [
    {
      "type": "DELETE"
    }
  ]
}

itemId 属性设置为已删除项的 ID。该项不再包含除其 ID 和 isDeleted 属性之外的元数据。

已选择自定义菜单项

用户选择了您的服务设置的自定义菜单项

{
  "collection": "timeline",
  "itemId": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation": "UPDATE",
  "userToken": "harold_penguin",
  "userActions": [
    {
      "type": "CUSTOM",
      "payload": "PING"
    }
  ]
}

itemId 属性设为用户选择的菜单项的 ID。

userActions 数组包含用户对此项执行的自定义操作的列表。您的服务应相应地处理这些操作。

位置信息更新

当前用户有新位置可用:

{
  "collection": "locations",
  "itemId": "latest",
  "operation": "UPDATE",
  "userToken": "harold_penguin",
  "verifyToken": "random_hash_to_verify_referer"
}

当您的 Glassware 应用收到位置信息更新时,请向 glass.locations.get 端点发送请求,以检索最新的已知位置。您的 Glassware 每 10 分钟会收到一次位置信息更新。

语音命令

用户已启用语音指令,例如“Ok Glass,记下 Cat Stream,Chipotle 的生日是明天”。系统会向您的 Glassware 发送以下通知:

{
  "collection": "timeline",
  "operation": "INSERT",
  "userToken": "chipotle's_owner",
  "verifyToken": "mew mew mew",
  "itemId": "<ITEM_ID>",
  "userActions": [
    {type: "LAUNCH"}
  ]
}

此通知可通过 userActions 属性中的 LAUNCH 值与其他通知区分开来。

然后,您可以使用 itemId 中的值提取时间轴项:

{
  "id": "<ITEM_ID>",
  "text": "Chipotle's birthday is tomorrow",
  "recipients": [
    {"id": "CAT_STREAM"}
  ]
}

recipients 属性包含联系人的 id,表示所用的语音指令。