Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
5ac6f931
Commit
5ac6f931
authored
Jan 20, 2016
by
Devon Carew
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1305 from devoncarew/device_notification
Device notification
parents
5ad67975
8bb8e1d9
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
240 additions
and
66 deletions
+240
-66
daemon.dart
packages/flutter_tools/lib/src/commands/daemon.dart
+181
-45
device.dart
packages/flutter_tools/lib/src/device.dart
+26
-10
daemon_test.dart
packages/flutter_tools/test/daemon_test.dart
+19
-6
daemon_client.dart
packages/flutter_tools/tool/daemon_client.dart
+14
-5
No files found.
packages/flutter_tools/lib/src/commands/daemon.dart
View file @
5ac6f931
...
...
@@ -6,21 +6,14 @@ import 'dart:async';
import
'dart:convert'
;
import
'dart:io'
;
import
'../android/adb.dart'
;
import
'../base/logging.dart'
;
import
'../device.dart'
;
import
'../runner/flutter_command.dart'
;
import
'start.dart'
;
import
'stop.dart'
;
const
String
protocolVersion
=
'0.0.1'
;
/// A @domain annotation.
const
String
domain
=
'domain'
;
/// A domain @command annotation.
const
String
command
=
'command'
;
// TODO: Create a `device` domain in order to list devices and fire events when
// devices are added or removed.
const
String
protocolVersion
=
'0.0.2'
;
/// A server process command. This command will start up a long-lived server.
/// It reads JSON-RPC based commands from stdin, executes them, and returns
...
...
@@ -32,7 +25,6 @@ class DaemonCommand extends FlutterCommand {
final
String
name
=
'daemon'
;
final
String
description
=
'Run a persistent, JSON-RPC based server to communicate with devices.'
;
@override
Future
<
int
>
runInProject
()
async
{
print
(
'Starting device daemon...'
);
...
...
@@ -48,90 +40,101 @@ class DaemonCommand extends FlutterCommand {
await
downloadApplicationPackagesAndConnectToDevices
();
Daemon
daemon
=
new
Daemon
(
commandStream
,
(
Map
command
)
{
stdout
.
writeln
(
'[
${JSON.encode(command)}
]'
);
stdout
.
writeln
(
'[
${JSON.encode(command
, toEncodable: _jsonEncodeObject
)}
]'
);
},
daemonCommand:
this
);
return
await
daemon
.
onExit
;
}
dynamic
_jsonEncodeObject
(
dynamic
object
)
{
if
(
object
is
Device
)
return
_deviceToMap
(
object
);
return
object
;
}
}
typedef
void
DispatchComand
(
Map
command
);
typedef
void
DispatchComand
(
Map
<
String
,
dynamic
>
command
);
typedef
Future
<
dynamic
>
CommandHandler
(
dynamic
args
);
class
Daemon
{
final
DispatchComand
sendCommand
;
final
DaemonCommand
daemonCommand
;
final
Completer
<
int
>
_onExitCompleter
=
new
Completer
();
final
Map
<
String
,
Domain
>
_domains
=
{};
Daemon
(
Stream
<
Map
>
commandStream
,
this
.
sendCommand
,
{
this
.
daemonCommand
})
{
// Set up domains.
_registerDomain
(
new
DaemonDomain
(
this
));
_registerDomain
(
new
AppDomain
(
this
));
_registerDomain
(
new
DeviceDomain
(
this
));
// Start listening.
commandStream
.
listen
(
(
Map
command
)
=>
_handleCommand
(
command
),
(
Map
request
)
=>
_handleRequest
(
request
),
onDone:
()
=>
_onExitCompleter
.
complete
(
0
)
);
}
final
DispatchComand
sendCommand
;
final
DaemonCommand
daemonCommand
;
final
Completer
<
int
>
_onExitCompleter
=
new
Completer
<
int
>();
final
Map
<
String
,
Domain
>
_domainMap
=
<
String
,
Domain
>{};
void
_registerDomain
(
Domain
domain
)
{
_domain
s
[
domain
.
name
]
=
domain
;
_domain
Map
[
domain
.
name
]
=
domain
;
}
Future
<
int
>
get
onExit
=>
_onExitCompleter
.
future
;
void
_handleCommand
(
Map
command
)
{
// {id, event, params}
var
id
=
command
[
'id'
];
void
_handleRequest
(
Map
request
)
{
// {id, method, params}
// [id] is an opaque type to us.
dynamic
id
=
request
[
'id'
];
if
(
id
==
null
)
{
logging
.
severe
(
'no id for
command:
$command
'
);
logging
.
severe
(
'no id for
request:
$request
'
);
return
;
}
try
{
String
event
=
command
[
'event
'
];
if
(
event
.
indexOf
(
'.'
)
==
-
1
)
throw
'
command not understood:
$event
'
;
String
method
=
request
[
'method
'
];
if
(
method
.
indexOf
(
'.'
)
==
-
1
)
throw
'
method not understood:
$method
'
;
String
prefix
=
event
.
substring
(
0
,
event
.
indexOf
(
'.'
));
String
name
=
event
.
substring
(
event
.
indexOf
(
'.'
)
+
1
);
if
(
_domain
s
[
prefix
]
==
null
)
throw
'no domain for
command:
$comman
d
'
;
String
prefix
=
method
.
substring
(
0
,
method
.
indexOf
(
'.'
));
String
name
=
method
.
substring
(
method
.
indexOf
(
'.'
)
+
1
);
if
(
_domain
Map
[
prefix
]
==
null
)
throw
'no domain for
method:
$metho
d
'
;
_domain
s
[
prefix
].
handleEvent
(
name
,
id
,
command
[
'params'
]);
_domain
Map
[
prefix
].
handleCommand
(
name
,
id
,
request
[
'params'
]);
}
catch
(
error
,
trace
)
{
_send
({
'id'
:
id
,
'error'
:
_toJsonable
(
error
)});
logging
.
warning
(
'error handling
$
{command['event']}
'
,
error
,
trace
);
logging
.
warning
(
'error handling
$
request
'
,
error
,
trace
);
}
}
void
_send
(
Map
map
)
=>
sendCommand
(
map
);
void
shutdown
()
{
_domainMap
.
values
.
forEach
((
Domain
domain
)
=>
domain
.
dispose
());
if
(!
_onExitCompleter
.
isCompleted
)
_onExitCompleter
.
complete
(
0
);
}
}
abstract
class
Domain
{
Domain
(
this
.
daemon
,
this
.
name
);
final
Daemon
daemon
;
final
String
name
;
final
Map
<
String
,
CommandHandler
>
_handlers
=
{};
Domain
(
this
.
daemon
,
this
.
name
);
void
registerHandler
(
String
name
,
CommandHandler
handler
)
{
_handlers
[
name
]
=
handler
;
}
String
toString
()
=>
name
;
void
handle
Event
(
String
name
,
dynamic
id
,
dynamic
args
)
{
void
handle
Command
(
String
name
,
dynamic
id
,
dynamic
args
)
{
new
Future
.
sync
(()
{
if
(
_handlers
.
containsKey
(
name
))
return
_handlers
[
name
](
args
);
...
...
@@ -148,24 +151,30 @@ abstract class Domain {
});
}
void
sendEvent
(
String
name
,
[
dynamic
args
])
{
Map
<
String
,
dynamic
>
map
=
{
'method'
:
name
};
if
(
args
!=
null
)
map
[
'params'
]
=
_toJsonable
(
args
);
_send
(
map
);
}
void
_send
(
Map
map
)
=>
daemon
.
_send
(
map
);
void
dispose
()
{
}
}
/// This domain responds to methods like [version] and [shutdown].
@domain
class
DaemonDomain
extends
Domain
{
DaemonDomain
(
Daemon
daemon
)
:
super
(
daemon
,
'daemon'
)
{
registerHandler
(
'version'
,
version
);
registerHandler
(
'shutdown'
,
shutdown
);
}
@command
Future
<
dynamic
>
version
(
dynamic
args
)
{
Future
<
String
>
version
(
dynamic
args
)
{
return
new
Future
.
value
(
protocolVersion
);
}
@command
Future
<
dynamic
>
shutdown
(
dynamic
args
)
{
Future
shutdown
(
dynamic
args
)
{
Timer
.
run
(()
=>
daemon
.
shutdown
());
return
new
Future
.
value
();
}
...
...
@@ -175,14 +184,12 @@ class DaemonDomain extends Domain {
///
/// It'll be extended to fire events for when applications start, stop, and
/// log data.
@domain
class
AppDomain
extends
Domain
{
AppDomain
(
Daemon
daemon
)
:
super
(
daemon
,
'app'
)
{
registerHandler
(
'start'
,
start
);
registerHandler
(
'stopAll'
,
stopAll
);
}
@command
Future
<
dynamic
>
start
(
dynamic
args
)
{
// TODO: Add the ability to pass args: target, http, checked
StartCommand
startComand
=
new
StartCommand
();
...
...
@@ -190,7 +197,6 @@ class AppDomain extends Domain {
return
startComand
.
runInProject
().
then
((
_
)
=>
null
);
}
@command
Future
<
bool
>
stopAll
(
dynamic
args
)
{
StopCommand
stopCommand
=
new
StopCommand
();
stopCommand
.
inheritFromParent
(
daemon
.
daemonCommand
);
...
...
@@ -198,8 +204,138 @@ class AppDomain extends Domain {
}
}
/// This domain lets callers list and monitor connected devices.
///
/// It exports a `getDevices()` call, as well as firing `device.added`,
/// `device.removed`, and `device.changed` events.
class
DeviceDomain
extends
Domain
{
DeviceDomain
(
Daemon
daemon
)
:
super
(
daemon
,
'device'
)
{
registerHandler
(
'getDevices'
,
getDevices
);
_androidDeviceDiscovery
=
new
AndroidDeviceDiscovery
();
_androidDeviceDiscovery
.
onAdded
.
listen
((
Device
device
)
{
sendEvent
(
'device.added'
,
_deviceToMap
(
device
));
});
_androidDeviceDiscovery
.
onRemoved
.
listen
((
Device
device
)
{
sendEvent
(
'device.removed'
,
_deviceToMap
(
device
));
});
_androidDeviceDiscovery
.
onChanged
.
listen
((
Device
device
)
{
sendEvent
(
'device.changed'
,
_deviceToMap
(
device
));
});
}
AndroidDeviceDiscovery
_androidDeviceDiscovery
;
Future
<
List
<
Device
>>
getDevices
(
dynamic
args
)
{
List
<
Device
>
devices
=
<
Device
>[];
devices
.
addAll
(
_androidDeviceDiscovery
.
getDevices
());
return
new
Future
.
value
(
devices
);
}
void
dispose
()
{
_androidDeviceDiscovery
.
dispose
();
}
}
class
AndroidDeviceDiscovery
{
AndroidDeviceDiscovery
()
{
_initAdb
();
if
(
_adb
!=
null
)
{
_subscription
=
_adb
.
trackDevices
().
listen
(
_handleNewDevices
);
}
}
Adb
_adb
;
StreamSubscription
_subscription
;
Map
<
String
,
AndroidDevice
>
_devices
=
new
Map
<
String
,
AndroidDevice
>();
StreamController
<
Device
>
addedController
=
new
StreamController
<
Device
>.
broadcast
();
StreamController
<
Device
>
removedController
=
new
StreamController
<
Device
>.
broadcast
();
StreamController
<
Device
>
changedController
=
new
StreamController
<
Device
>.
broadcast
();
List
<
Device
>
getDevices
()
=>
_devices
.
values
.
toList
();
Stream
<
Device
>
get
onAdded
=>
addedController
.
stream
;
Stream
<
Device
>
get
onRemoved
=>
removedController
.
stream
;
Stream
<
Device
>
get
onChanged
=>
changedController
.
stream
;
void
_initAdb
()
{
if
(
_adb
==
null
)
{
_adb
=
new
Adb
(
AndroidDevice
.
getAdbPath
());
if
(!
_adb
.
exists
())
_adb
=
null
;
}
}
void
_handleNewDevices
(
List
<
AdbDevice
>
newDevices
)
{
List
<
AndroidDevice
>
currentDevices
=
new
List
.
from
(
getDevices
());
for
(
AdbDevice
device
in
newDevices
)
{
AndroidDevice
androidDevice
=
_devices
[
device
.
id
];
if
(
androidDevice
==
null
)
{
// device added
androidDevice
=
new
AndroidDevice
(
id:
device
.
id
,
productID:
device
.
productID
,
modelID:
device
.
modelID
,
deviceCodeName:
device
.
deviceCodeName
,
connected:
device
.
isAvailable
);
_devices
[
androidDevice
.
id
]
=
androidDevice
;
addedController
.
add
(
androidDevice
);
}
else
{
currentDevices
.
remove
(
androidDevice
);
// check state
if
(
androidDevice
.
isConnected
()
!=
device
.
isAvailable
)
{
androidDevice
.
setConnected
(
device
.
isAvailable
);
changedController
.
add
(
androidDevice
);
}
}
}
// device removed
for
(
AndroidDevice
device
in
currentDevices
)
{
_devices
.
remove
(
device
.
id
);
// I don't know the purpose of this cache or if it's a good idea. We should
// probably have a DeviceManager singleton class to coordinate known devices
// and different device discovery mechanisms.
Device
.
removeFromCache
(
device
.
id
);
removedController
.
add
(
device
);
}
}
void
dispose
()
{
_subscription
?.
cancel
();
}
}
Map
<
String
,
dynamic
>
_deviceToMap
(
Device
device
)
{
return
<
String
,
dynamic
>{
'id'
:
device
.
id
,
'platform'
:
_enumToString
(
device
.
platform
),
'available'
:
device
.
isConnected
()
};
}
/// Take an enum value and get the best string representation of that.
///
/// toString() on enums returns 'EnumType.enumName'.
String
_enumToString
(
dynamic
enumValue
)
{
String
str
=
'
$enumValue
'
;
if
(
str
.
contains
(
'.'
))
return
str
.
substring
(
str
.
indexOf
(
'.'
)
+
1
);
return
str
;
}
dynamic
_toJsonable
(
dynamic
obj
)
{
if
(
obj
is
String
||
obj
is
int
||
obj
is
bool
||
obj
is
Map
||
obj
is
List
||
obj
==
null
)
return
obj
;
if
(
obj
is
Device
)
return
obj
;
return
'
$obj
'
;
}
packages/flutter_tools/lib/src/device.dart
View file @
5ac6f931
...
...
@@ -21,8 +21,14 @@ abstract class Device {
return
_deviceCache
.
putIfAbsent
(
id
,
()
=>
constructor
(
id
));
}
static
void
removeFromCache
(
String
id
)
{
_deviceCache
.
remove
(
id
);
}
Device
.
_
(
this
.
id
);
String
get
name
;
/// Install an app package on the current device
bool
installApp
(
ApplicationPackage
app
);
...
...
@@ -532,20 +538,25 @@ class AndroidDevice extends Device {
String
modelID
;
String
deviceCodeName
;
bool
_connected
;
String
_adbPath
;
String
get
adbPath
=>
_adbPath
;
bool
_hasAdb
=
false
;
bool
_hasValidAndroid
=
false
;
factory
AndroidDevice
(
{
String
id:
null
,
String
productID:
null
,
String
modelID:
null
,
String
deviceCodeName:
null
})
{
factory
AndroidDevice
({
String
id:
null
,
String
productID:
null
,
String
modelID:
null
,
String
deviceCodeName:
null
,
bool
connected
})
{
AndroidDevice
device
=
Device
.
_unique
(
id
??
defaultDeviceID
,
(
String
id
)
=>
new
AndroidDevice
.
_
(
id
));
device
.
productID
=
productID
;
device
.
modelID
=
modelID
;
device
.
deviceCodeName
=
deviceCodeName
;
if
(
connected
!=
null
)
device
.
_connected
=
connected
;
return
device
;
}
...
...
@@ -553,7 +564,7 @@ class AndroidDevice extends Device {
/// we don't have to rely on the test setup having adb available to it.
static
List
<
AndroidDevice
>
getAttachedDevices
([
AndroidDevice
mockAndroid
])
{
List
<
AndroidDevice
>
devices
=
[];
String
adbPath
=
(
mockAndroid
!=
null
)
?
mockAndroid
.
adbPath
:
_
getAdbPath
();
String
adbPath
=
(
mockAndroid
!=
null
)
?
mockAndroid
.
adbPath
:
getAdbPath
();
try
{
runCheckedSync
([
adbPath
,
'version'
]);
...
...
@@ -623,7 +634,7 @@ class AndroidDevice extends Device {
}
AndroidDevice
.
_
(
id
)
:
super
.
_
(
id
)
{
_adbPath
=
_
getAdbPath
();
_adbPath
=
getAdbPath
();
_hasAdb
=
_checkForAdb
();
// Checking for [minApiName] only needs to be done if we are starting an
...
...
@@ -655,7 +666,7 @@ class AndroidDevice extends Device {
}
}
static
String
_
getAdbPath
()
{
static
String
getAdbPath
()
{
if
(
Platform
.
environment
.
containsKey
(
'ANDROID_HOME'
))
{
String
androidHomeDir
=
Platform
.
environment
[
'ANDROID_HOME'
];
String
adbPath1
=
path
.
join
(
androidHomeDir
,
'sdk'
,
'platform-tools'
,
'adb'
);
...
...
@@ -782,6 +793,8 @@ class AndroidDevice extends Device {
return
CryptoUtils
.
bytesToHex
(
sha1
.
close
());
}
String
get
name
=>
modelID
;
@override
bool
isAppInstalled
(
ApplicationPackage
app
)
{
if
(!
isConnected
())
{
...
...
@@ -992,8 +1005,11 @@ class AndroidDevice extends Device {
return
null
;
}
@override
bool
isConnected
()
=>
_hasValidAndroid
;
bool
isConnected
()
=>
_connected
!=
null
?
_connected
:
_hasValidAndroid
;
void
setConnected
(
bool
value
)
{
_connected
=
value
;
}
}
class
DeviceStore
{
...
...
packages/flutter_tools/test/daemon_test.dart
View file @
5ac6f931
...
...
@@ -26,9 +26,9 @@ defineTests() {
StreamController
<
Map
>
responses
=
new
StreamController
();
daemon
=
new
Daemon
(
commands
.
stream
,
(
Map
result
)
=>
responses
.
add
(
result
)
(
Map
<
String
,
dynamic
>
result
)
=>
responses
.
add
(
result
)
);
commands
.
add
({
'id'
:
0
,
'
event
'
:
'daemon.version'
});
commands
.
add
({
'id'
:
0
,
'
method
'
:
'daemon.version'
});
Map
response
=
await
responses
.
stream
.
first
;
expect
(
response
[
'id'
],
0
);
expect
(
response
[
'result'
],
isNotEmpty
);
...
...
@@ -40,9 +40,9 @@ defineTests() {
StreamController
<
Map
>
responses
=
new
StreamController
();
daemon
=
new
Daemon
(
commands
.
stream
,
(
Map
result
)
=>
responses
.
add
(
result
)
(
Map
<
String
,
dynamic
>
result
)
=>
responses
.
add
(
result
)
);
commands
.
add
({
'id'
:
0
,
'
event
'
:
'daemon.shutdown'
});
commands
.
add
({
'id'
:
0
,
'
method
'
:
'daemon.shutdown'
});
return
daemon
.
onExit
.
then
((
int
code
)
{
expect
(
code
,
0
);
});
...
...
@@ -56,7 +56,7 @@ defineTests() {
StreamController
<
Map
>
responses
=
new
StreamController
();
daemon
=
new
Daemon
(
commands
.
stream
,
(
Map
result
)
=>
responses
.
add
(
result
),
(
Map
<
String
,
dynamic
>
result
)
=>
responses
.
add
(
result
),
daemonCommand:
command
);
...
...
@@ -71,10 +71,23 @@ defineTests() {
when
(
mockDevices
.
iOSSimulator
.
isConnected
()).
thenReturn
(
false
);
when
(
mockDevices
.
iOSSimulator
.
stopApp
(
any
)).
thenReturn
(
false
);
commands
.
add
({
'id'
:
0
,
'
event
'
:
'app.stopAll'
});
commands
.
add
({
'id'
:
0
,
'
method
'
:
'app.stopAll'
});
Map
response
=
await
responses
.
stream
.
first
;
expect
(
response
[
'id'
],
0
);
expect
(
response
[
'result'
],
true
);
});
test
(
'device.getDevices'
,
()
async
{
StreamController
<
Map
>
commands
=
new
StreamController
();
StreamController
<
Map
>
responses
=
new
StreamController
();
daemon
=
new
Daemon
(
commands
.
stream
,
(
Map
<
String
,
dynamic
>
result
)
=>
responses
.
add
(
result
)
);
commands
.
add
({
'id'
:
0
,
'method'
:
'device.getDevices'
});
Map
response
=
await
responses
.
stream
.
first
;
expect
(
response
[
'id'
],
0
);
expect
(
response
[
'result'
],
isList
);
});
});
}
packages/flutter_tools/tool/daemon_client.dart
View file @
5ac6f931
...
...
@@ -7,8 +7,15 @@ import 'dart:io';
Process
daemon
;
// To use, start from the console and enter:
// version: print version
// shutdown: terminate the server
// start: start an app
// stopAll: stop any running app
// devices: list devices
main
()
async
{
daemon
=
await
Process
.
start
(
'
dart'
,
[
'bin/flutter_tools.dart'
,
'daemon'
]);
daemon
=
await
Process
.
start
(
'
flutter'
,
[
'daemon'
]);
print
(
'daemon process started, pid:
${daemon.pid}
'
);
daemon
.
stdout
...
...
@@ -20,13 +27,15 @@ main() async {
stdout
.
write
(
'> '
);
stdin
.
transform
(
UTF8
.
decoder
).
transform
(
const
LineSplitter
()).
listen
((
String
line
)
{
if
(
line
==
'version'
||
line
==
'v'
)
{
_send
({
'
event
'
:
'daemon.version'
});
_send
({
'
method
'
:
'daemon.version'
});
}
else
if
(
line
==
'shutdown'
||
line
==
'q'
)
{
_send
({
'
event
'
:
'daemon.shutdown'
});
_send
({
'
method
'
:
'daemon.shutdown'
});
}
else
if
(
line
==
'start'
)
{
_send
({
'
event
'
:
'app.start'
});
_send
({
'
method
'
:
'app.start'
});
}
else
if
(
line
==
'stopAll'
)
{
_send
({
'event'
:
'app.stopAll'
});
_send
({
'method'
:
'app.stopAll'
});
}
else
if
(
line
==
'devices'
)
{
_send
({
'method'
:
'device.getDevices'
});
}
else
{
print
(
'command not understood:
${line}
'
);
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment