Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions commands/animation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package commands

import (
"fmt"

"github.com/mobile-next/mobilecli/devices"
)

// AnimationScalesGetRequest represents the request for getting animation scales
type AnimationScalesGetRequest struct {
DeviceID string `json:"deviceId"`
}

// AnimationScalesSetRequest represents the request for setting animation scales
type AnimationScalesSetRequest struct {
DeviceID string `json:"deviceId"`
Window float64 `json:"window"`
Transition float64 `json:"transition"`
Animator float64 `json:"animator"`
}

// AnimationScalesResponse holds the three Android global animation scale values
type AnimationScalesResponse struct {
Window float64 `json:"window"`
Transition float64 `json:"transition"`
Animator float64 `json:"animator"`
}

// AnimationScalesGetCommand gets the current animation scale values from the device
func AnimationScalesGetCommand(req AnimationScalesGetRequest) *CommandResponse {
device, err := FindDeviceOrAutoSelect(req.DeviceID)
if err != nil {
return NewErrorResponse(err)
}

err = device.StartAgent(devices.StartAgentConfig{
Hook: GetShutdownHook(),
})
if err != nil {
return NewErrorResponse(fmt.Errorf("failed to start agent on device %s: %v", device.ID(), err))
}

scales, err := device.GetAnimationScales()
if err != nil {
return NewErrorResponse(fmt.Errorf("failed to get animation scales: %v", err))
}

return NewSuccessResponse(AnimationScalesResponse{
Window: scales.Window,
Transition: scales.Transition,
Animator: scales.Animator,
})
}

// AnimationScalesSetCommand sets the animation scale values on the device
func AnimationScalesSetCommand(req AnimationScalesSetRequest) *CommandResponse {
device, err := FindDeviceOrAutoSelect(req.DeviceID)
if err != nil {
return NewErrorResponse(err)
}

err = device.StartAgent(devices.StartAgentConfig{
Hook: GetShutdownHook(),
})
if err != nil {
return NewErrorResponse(fmt.Errorf("failed to start agent on device %s: %v", device.ID(), err))
}

err = device.SetAnimationScales(devices.AnimationScales{
Window: req.Window,
Transition: req.Transition,
Animator: req.Animator,
})
if err != nil {
return NewErrorResponse(fmt.Errorf("failed to set animation scales: %v", err))
}

return NewSuccessResponse(AnimationScalesResponse{
Window: req.Window,
Transition: req.Transition,
Animator: req.Animator,
})
}
47 changes: 47 additions & 0 deletions devices/android.go
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,53 @@ func (d *AndroidDevice) SetOrientation(orientation string) error {
return nil
}

// GetAnimationScales reads the current values of all three global animation
// scales. Callers should save the result and pass it to SetAnimationScales to
// restore the original values rather than assuming they were 1.
func (d *AndroidDevice) GetAnimationScales() (AnimationScales, error) {
keys := []string{"window_animation_scale", "transition_animation_scale", "animator_duration_scale"}
values := make([]float64, len(keys))

for i, key := range keys {
out, err := d.runAdbCommand("shell", "settings", "get", "global", key)
if err != nil {
return AnimationScales{}, fmt.Errorf("failed to get %s: %v", key, err)
}
val, err := strconv.ParseFloat(strings.TrimSpace(string(out)), 64)
if err != nil {
return AnimationScales{}, fmt.Errorf("failed to parse %s value %q: %v", key, strings.TrimSpace(string(out)), err)
}
values[i] = val
}

return AnimationScales{
Window: values[0],
Transition: values[1],
Animator: values[2],
}, nil
}

// SetAnimationScales writes the three global animation scales.
// Pass the result of GetAnimationScales to restore original values.
func (d *AndroidDevice) SetAnimationScales(scales AnimationScales) error {
entries := []struct {
key string
value float64
}{
{"window_animation_scale", scales.Window},
{"transition_animation_scale", scales.Transition},
{"animator_duration_scale", scales.Animator},
}

for _, e := range entries {
_, err := d.runAdbCommand("shell", "settings", "put", "global", e.key, strconv.FormatFloat(e.value, 'f', -1, 64))
if err != nil {
return fmt.Errorf("failed to set %s: %v", e.key, err)
}
}
return nil
}

func (d *AndroidDevice) getCrashLog() (string, error) {
output, err := d.runAdbCommand("logcat", "-b", "crash", "-d", "-v", "year")
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions devices/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,20 @@ type ControllableDevice interface {
DumpSourceRaw() (any, error)
GetOrientation() (string, error)
SetOrientation(orientation string) error
GetAnimationScales() (AnimationScales, error)
SetAnimationScales(scales AnimationScales) error
ListCrashReports() ([]CrashReport, error)
GetCrashReport(id string) ([]byte, error)
}

// AnimationScales holds the three Android global animation scale values.
// A value of 0 disables the animation; 1 is the system default.
type AnimationScales struct {
Window float64 `json:"window"`
Transition float64 `json:"transition"`
Animator float64 `json:"animator"`
}

// GetAllControllableDevices aggregates all known devices with options
func GetAllControllableDevices(includeOffline bool) ([]ControllableDevice, error) {

Expand Down
10 changes: 10 additions & 0 deletions devices/ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -1142,6 +1142,16 @@ func (d IOSDevice) SetOrientation(orientation string) error {
return d.wdaClient.SetOrientation(orientation)
}

// GetAnimationScales is not supported on iOS devices.
func (d IOSDevice) GetAnimationScales() (AnimationScales, error) {
return AnimationScales{}, fmt.Errorf("animation scales are not supported on iOS")
}

// SetAnimationScales is not supported on iOS devices.
func (d IOSDevice) SetAnimationScales(_ AnimationScales) error {
return fmt.Errorf("animation scales are not supported on iOS")
}

// DeviceKitInfo contains information about the started DeviceKit session
type DeviceKitInfo struct {
HTTPPort int `json:"httpPort"`
Expand Down
18 changes: 18 additions & 0 deletions devices/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,24 @@ func (r *RemoteDevice) SetOrientation(orientation string) error {
return r.fireRPC("device.io.orientation.set", params{"orientation": orientation})
}

// GetAnimationScales retrieves the current animation scale values from the remote device.
func (r *RemoteDevice) GetAnimationScales() (AnimationScales, error) {
resp, err := rpcCall[AnimationScales](r, "device.io.animation-scales.get", params{})
if err != nil {
return AnimationScales{}, err
}
return resp, nil
}

// SetAnimationScales sets the animation scale values on the remote device.
func (r *RemoteDevice) SetAnimationScales(scales AnimationScales) error {
return r.fireRPC("device.io.animation-scales.set", params{
"window": scales.Window,
"transition": scales.Transition,
"animator": scales.Animator,
})
}

func (r *RemoteDevice) Info() (*FullDeviceInfo, error) {
return rpcCall[*FullDeviceInfo](r, "device.info", params{})
}
Expand Down
10 changes: 10 additions & 0 deletions devices/simulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,16 @@ func (s SimulatorDevice) SetOrientation(orientation string) error {
return s.wdaClient.SetOrientation(orientation)
}

// GetAnimationScales is not supported on iOS simulators.
func (s SimulatorDevice) GetAnimationScales() (AnimationScales, error) {
return AnimationScales{}, fmt.Errorf("animation scales are not supported on iOS simulator")
}

// SetAnimationScales is not supported on iOS simulators.
func (s SimulatorDevice) SetAnimationScales(_ AnimationScales) error {
return fmt.Errorf("animation scales are not supported on iOS simulator")
}

var diagnosticReportsDir = filepath.Join(os.Getenv("HOME"), "Library", "Logs", "DiagnosticReports")

func (s SimulatorDevice) ListCrashReports() ([]CrashReport, error) {
Expand Down
6 changes: 4 additions & 2 deletions server/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ func GetMethodRegistry() map[string]HandlerFunc {
"device.io.gesture": handleIoGesture,
"device.url": handleURL,
"device.info": handleDeviceInfo,
"device.io.orientation.get": handleIoOrientationGet,
"device.io.orientation.set": handleIoOrientationSet,
"device.io.orientation.get": handleIoOrientationGet,
"device.io.orientation.set": handleIoOrientationSet,
"device.io.animation-scales.get": handleIoAnimationScalesGet,
"device.io.animation-scales.set": handleIoAnimationScalesSet,
"device.boot": handleDeviceBoot,
"device.shutdown": handleDeviceShutdown,
"device.reboot": handleDeviceReboot,
Expand Down
56 changes: 56 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,17 @@ type IoOrientationSetParams struct {
Orientation string `json:"orientation"`
}

type IoAnimationScalesGetParams struct {
DeviceID string `json:"deviceId"`
}

type IoAnimationScalesSetParams struct {
DeviceID string `json:"deviceId"`
Window float64 `json:"window"`
Transition float64 `json:"transition"`
Animator float64 `json:"animator"`
}

type DeviceBootParams struct {
DeviceID string `json:"deviceId"`
}
Expand Down Expand Up @@ -784,6 +795,51 @@ func handleIoOrientationSet(params json.RawMessage) (any, error) {
return okResponse, nil
}

// handleIoAnimationScalesGet handles the device.io.animation-scales.get RPC method.
func handleIoAnimationScalesGet(params json.RawMessage) (any, error) {
if len(params) == 0 {
return nil, fmt.Errorf("'params' is required with fields: deviceId")
}

var p IoAnimationScalesGetParams
if err := json.Unmarshal(params, &p); err != nil {
return nil, fmt.Errorf("invalid parameters: %w. Expected fields: deviceId", err)
}

response := commands.AnimationScalesGetCommand(commands.AnimationScalesGetRequest{
DeviceID: p.DeviceID,
})
if response.Status == "error" {
return nil, fmt.Errorf("%s", response.Error)
}

return response.Data, nil
}

// handleIoAnimationScalesSet handles the device.io.animation-scales.set RPC method.
func handleIoAnimationScalesSet(params json.RawMessage) (any, error) {
if len(params) == 0 {
return nil, fmt.Errorf("'params' is required with fields: deviceId, window, transition, animator")
}

var p IoAnimationScalesSetParams
if err := json.Unmarshal(params, &p); err != nil {
return nil, fmt.Errorf("invalid parameters: %w. Expected fields: deviceId, window, transition, animator", err)
}

response := commands.AnimationScalesSetCommand(commands.AnimationScalesSetRequest{
DeviceID: p.DeviceID,
Window: p.Window,
Transition: p.Transition,
Animator: p.Animator,
})
if response.Status == "error" {
return nil, fmt.Errorf("%s", response.Error)
}

return okResponse, nil
}

func handleDeviceBoot(params json.RawMessage) (any, error) {
if len(params) == 0 {
return nil, fmt.Errorf("'params' is required with fields: deviceId")
Expand Down