Skip to content

Commit 48667c2

Browse files
committed
Add viewport utilities for Alexa Presentation Language
1 parent 1875b5b commit 48667c2

14 files changed

+795
-15
lines changed

README.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ boiler-plate code.
2929
.. |Docs| image:: https://img.shields.io/readthedocs/alexa-skills-kit-python-sdk.svg?style=flat
3030
:target: https://alexa-skills-kit-python-sdk.readthedocs.io
3131
:alt: Read the docs
32+
.. |Runtime Version| image:: http://img.shields.io/pypi/v/ask-sdk-runtime.svg?style=flat
33+
:target: https://pypi.python.org/pypi/ask-sdk-runtime/
34+
:alt: Version
35+
.. |Runtime Downloads| image:: https://pepy.tech/badge/ask-sdk-runtime
36+
:target: https://pepy.tech/project/ask-sdk-runtime
37+
:alt: Downloads
3238
.. |Core Version| image:: http://img.shields.io/pypi/v/ask-sdk-core.svg?style=flat
3339
:target: https://pypi.python.org/pypi/ask-sdk-core/
3440
:alt: Version
@@ -56,6 +62,7 @@ Package Versions
5662
==================================== ==================
5763
Package Version
5864
------------------------------------ ------------------
65+
ask-sdk-runtime |Runtime Version| |Runtime Downloads|
5966
ask-sdk-core |Core Version| |Core Downloads|
6067
ask-sdk-dynamodb-persistence-adapter |DynamoDb Version| |DynamoDb Downloads|
6168
ask-sdk |Standard Version| |Standard Downloads|
@@ -236,6 +243,8 @@ Preview
236243

237244
* `Connections <https://developer.amazon.com/blogs/alexa/post/7b332b32-893e-4cad-be07-a5877efcbbb4/skill-connections-preview-now-skills-can-work-together-to-help-customers-get-more-done>`__
238245

246+
* `Alexa Presentation Language <https://developer.amazon.com/blogs/alexa/post/1dee3fa0-8c5f-4179-ab7a-74545ead24ce/introducing-the-alexa-presentation-language-preview>`__
247+
239248

240249
Got Feedback?
241250
-------------
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights
4+
# Reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License").
7+
# You may not use this file except in compliance with the License.
8+
# A copy of the License is located at
9+
#
10+
# http://aws.amazon.com/apache2.0/
11+
#
12+
# or in the "license" file accompanying this file. This file is
13+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
14+
# OF ANY KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations under the
16+
# License.
17+
#
18+
import sys
19+
20+
from ..__version__ import __version__
21+
from .predicate import is_intent_name, is_request_type
22+
from ask_sdk_runtime.utils import user_agent_info
23+
24+
25+
SDK_VERSION = __version__
26+
RESPONSE_FORMAT_VERSION = "1.0"

ask-sdk-core/ask_sdk_core/utils.py renamed to ask-sdk-core/ask_sdk_core/utils/predicate.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,10 @@
1818
import typing
1919

2020
from ask_sdk_model import IntentRequest
21-
from ask_sdk_runtime.utils import user_agent_info
22-
23-
from .__version__ import __version__
2421

2522
if typing.TYPE_CHECKING:
2623
from typing import Callable
27-
from .handler_input import HandlerInput
28-
29-
30-
SDK_VERSION = __version__
31-
RESPONSE_FORMAT_VERSION = "1.0"
24+
from ..handler_input import HandlerInput
3225

3326

3427
def is_intent_name(name):
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights
4+
# Reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License").
7+
# You may not use this file except in compliance with the License.
8+
# A copy of the License is located at
9+
#
10+
# http://aws.amazon.com/apache2.0/
11+
#
12+
# or in the "license" file accompanying this file. This file is
13+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
14+
# OF ANY KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations under the
16+
# License.
17+
#
18+
from enum import Enum
19+
from ask_sdk_model import RequestEnvelope
20+
from ask_sdk_model.interfaces.viewport import Shape
21+
22+
from ..exceptions import AskSdkException
23+
24+
25+
class OrderedEnum(Enum):
26+
def __ge__(self, other):
27+
if self.__class__ is other.__class__:
28+
return self.value >= other.value
29+
return NotImplemented
30+
31+
def __gt__(self, other):
32+
if self.__class__ is other.__class__:
33+
return self.value > other.value
34+
return NotImplemented
35+
36+
def __le__(self, other):
37+
if self.__class__ is other.__class__:
38+
return self.value <= other.value
39+
return NotImplemented
40+
41+
def __lt__(self, other):
42+
if self.__class__ is other.__class__:
43+
return self.value < other.value
44+
return NotImplemented
45+
46+
47+
class Density(OrderedEnum):
48+
XLOW = 0
49+
LOW = 1
50+
MEDIUM = 2
51+
HIGH = 3
52+
XHIGH = 4
53+
XXHIGH = 5
54+
55+
56+
class Orientation(OrderedEnum):
57+
LANDSCAPE = 0
58+
EQUAL = 1
59+
PORTRAIT = 2
60+
61+
62+
class Size(OrderedEnum):
63+
XSMALL = 0
64+
SMALL = 1
65+
MEDIUM = 2
66+
LARGE = 3
67+
XLARGE = 4
68+
69+
70+
class ViewportProfile(Enum):
71+
HUB_ROUND_SMALL = "HUB_ROUND_SMALL"
72+
HUB_LANDSCAPE_MEDIUM = "HUB_LANDSCAPE_MEDIUM"
73+
HUB_LANDSCAPE_LARGE = "HUB_LANDSCAPE_LARGE"
74+
MOBILE_LANDSCAPE_SMALL = "MOBILE_LANDSCAPE_SMALL"
75+
MOBILE_PORTRAIT_SMALL = "MOBILE_PORTRAIT_SMALL"
76+
MOBILE_LANDSCAPE_MEDIUM = "MOBILE_LANDSCAPE_MEDIUM"
77+
MOBILE_PORTRAIT_MEDIUM = "MOBILE_PORTRAIT_MEDIUM"
78+
TV_LANDSCAPE_XLARGE = "TV_LANDSCAPE_XLARGE"
79+
TV_PORTRAIT_MEDIUM = "TV_PORTRAIT_MEDIUM"
80+
TV_LANDSCAPE_MEDIUM = "TV_LANDSCAPE_MEDIUM"
81+
UNKNOWN_VIEWPORT_PROFILE = "UNKNOWN_VIEWPORT_PROFILE"
82+
83+
84+
def get_orientation(width, height):
85+
# type: (int, int) -> Orientation
86+
"""Get viewport orientation from given width and height.
87+
88+
:type width: int
89+
:type height: int
90+
:return viewport orientation enum
91+
:rtype: Orientation
92+
"""
93+
if width > height:
94+
return Orientation.LANDSCAPE
95+
elif width < height:
96+
return Orientation.PORTRAIT
97+
else:
98+
return Orientation.EQUAL
99+
100+
101+
def get_size(size):
102+
# type: (int) -> Size
103+
"""Get viewport size from given size.
104+
105+
:type size: int
106+
:return viewport size enum
107+
:rtype: Size
108+
"""
109+
if size in range(0, 600):
110+
return Size.XSMALL
111+
elif size in range(600, 960):
112+
return Size.SMALL
113+
elif size in range(960, 1280):
114+
return Size.MEDIUM
115+
elif size in range(1280, 1920):
116+
return Size.LARGE
117+
elif size >= 1920:
118+
return Size.XLARGE
119+
120+
raise AskSdkException("Unknown size group value: {}".format(size))
121+
122+
123+
def get_dpi_group(dpi):
124+
# type: (int) -> Density
125+
"""Get viewport density group from given dpi.
126+
127+
:type dpi: int
128+
:return viewport density group enum
129+
:rtype: Density
130+
"""
131+
if dpi in range(0, 121):
132+
return Density.XLOW
133+
elif dpi in range(121, 161):
134+
return Density.LOW
135+
elif dpi in range(161, 241):
136+
return Density.MEDIUM
137+
elif dpi in range(241, 321):
138+
return Density.HIGH
139+
elif dpi in range(321, 481):
140+
return Density.XHIGH
141+
elif dpi >= 481:
142+
return Density.XXHIGH
143+
144+
raise AskSdkException("Unknown dpi group value: {}".format(dpi))
145+
146+
147+
def get_viewport_profile(request_envelope):
148+
# type: (RequestEnvelope) -> ViewportProfile
149+
"""Utility method, to get viewport profile.
150+
151+
The viewport profile is calculated using the shape, current pixel
152+
width and height, along with the dpi.
153+
154+
If there is no `viewport`
155+
value in `request_envelope.context`, then an
156+
`ViewportProfile.UNKNOWN_VIEWPORT_PROFILE` is returned.
157+
158+
:param request_envelope: The alexa request envelope object
159+
:type request_envelope: ask_sdk_model.request_envelope.RequestEnvelope
160+
:return Calculated Viewport Profile enum
161+
:rtype ViewportProfile
162+
"""
163+
viewport_state = request_envelope.context.viewport
164+
if viewport_state:
165+
shape = viewport_state.shape
166+
current_pixel_width = int(viewport_state.current_pixel_width)
167+
current_pixel_height = int(viewport_state.current_pixel_height)
168+
dpi = int(viewport_state.dpi)
169+
170+
orientation = get_orientation(
171+
width=current_pixel_width, height=current_pixel_height)
172+
dpi_group = get_dpi_group(dpi=dpi)
173+
pixel_width_size_group = get_size(size=current_pixel_width)
174+
pixel_height_size_group = get_size(size=current_pixel_height)
175+
176+
if (shape is Shape.ROUND
177+
and orientation is Orientation.EQUAL
178+
and dpi_group is Density.LOW
179+
and pixel_width_size_group is Size.XSMALL
180+
and pixel_height_size_group is Size.XSMALL):
181+
return ViewportProfile.HUB_ROUND_SMALL
182+
183+
elif (shape is Shape.RECTANGLE
184+
and orientation is Orientation.LANDSCAPE
185+
and dpi_group is Density.LOW
186+
and pixel_width_size_group <= Size.MEDIUM
187+
and pixel_height_size_group <= Size.SMALL):
188+
return ViewportProfile.HUB_LANDSCAPE_MEDIUM
189+
190+
elif (shape is Shape.RECTANGLE
191+
and orientation is Orientation.LANDSCAPE
192+
and dpi_group is Density.LOW
193+
and pixel_width_size_group >= Size.LARGE
194+
and pixel_height_size_group >= Size.SMALL):
195+
return ViewportProfile.HUB_LANDSCAPE_LARGE
196+
197+
elif (shape is Shape.RECTANGLE
198+
and orientation is Orientation.LANDSCAPE
199+
and dpi_group is Density.MEDIUM
200+
and pixel_width_size_group >= Size.MEDIUM
201+
and pixel_height_size_group >= Size.SMALL):
202+
return ViewportProfile.MOBILE_LANDSCAPE_MEDIUM
203+
204+
elif (shape is Shape.RECTANGLE
205+
and orientation is Orientation.PORTRAIT
206+
and dpi_group is Density.MEDIUM
207+
and pixel_width_size_group >= Size.SMALL
208+
and pixel_height_size_group >= Size.MEDIUM):
209+
return ViewportProfile.MOBILE_PORTRAIT_MEDIUM
210+
211+
elif (shape is Shape.RECTANGLE
212+
and orientation is Orientation.LANDSCAPE
213+
and dpi_group is Density.MEDIUM
214+
and pixel_width_size_group >= Size.SMALL
215+
and pixel_height_size_group >= Size.XSMALL):
216+
return ViewportProfile.MOBILE_LANDSCAPE_SMALL
217+
218+
elif (shape is Shape.RECTANGLE
219+
and orientation is Orientation.PORTRAIT
220+
and dpi_group is Density.MEDIUM
221+
and pixel_width_size_group >= Size.XSMALL
222+
and pixel_height_size_group >= Size.SMALL):
223+
return ViewportProfile.MOBILE_PORTRAIT_SMALL
224+
225+
elif (shape is Shape.RECTANGLE
226+
and orientation is Orientation.LANDSCAPE
227+
and dpi_group >= Density.HIGH
228+
and pixel_width_size_group >= Size.XLARGE
229+
and pixel_height_size_group >= Size.MEDIUM):
230+
return ViewportProfile.TV_LANDSCAPE_XLARGE
231+
232+
elif (shape is Shape.RECTANGLE
233+
and orientation is Orientation.PORTRAIT
234+
and dpi_group >= Density.HIGH
235+
and pixel_width_size_group is Size.XSMALL
236+
and pixel_height_size_group is Size.XLARGE):
237+
return ViewportProfile.TV_PORTRAIT_MEDIUM
238+
239+
elif (shape is Shape.RECTANGLE
240+
and orientation is Orientation.LANDSCAPE
241+
and dpi_group >= Density.HIGH
242+
and pixel_width_size_group is Size.MEDIUM
243+
and pixel_height_size_group is Size.SMALL):
244+
return ViewportProfile.TV_LANDSCAPE_MEDIUM
245+
246+
return ViewportProfile.UNKNOWN_VIEWPORT_PROFILE

ask-sdk-core/requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
requests
22
python_dateutil
3-
ask-sdk-model
4-
ask-sdk-runtime
3+
ask-sdk-model>=1.0.0
4+
ask-sdk-runtime>=1.1.0

0 commit comments

Comments
 (0)