Android-ElectricSheep-Automator
view release on metacpan or search on metacpan
lib/Android/ElectricSheep/Automator/Plugins/Apps/Viber.pm view on Meta::CPAN
use Time::HiRes qw/usleep/;
use Encode qw/encode_utf8 decode_utf8/;
use Data::Roundtrip qw/perl2dump no-unicode-escape-permanently/;
use Android::ElectricSheep::Automator::XMLParsers;
sub new {
my ($class, $params) = @_;
my $self = $class->SUPER::new({
%$params,
'child-class' => $class,
});
$self->{'_private'}->{'appname'} = 'com.viber.voip';
return $self;
}
# keeps pressing the back-arrow at the top of the app to hopefully
# arrive at the main activity of the app,
# TODO: is there a way to tell it to go to main activity ? WelcomeActivity does not seem to work
# returns 1 on failure, 0 on success.
sub navigate_to_viber_home_activity {
my ($self, $params) = @_;
my $parent = ( caller(1) )[3] || "N/A";
my $whoami = ( caller(0) )[3];
my $log = $self->log();
my $verbosity = $self->verbosity();
my ($outbase, $outfile);
# for debugging purposes, save each UI we get here
$outbase = exists($params->{'outbase'}) ? $params->{'outbase'} : undef;
my ($ui, $dom, $xc, $asel, @nodes, $N, $node, $boundstr, $bounds);
my $repeats = 3;
my $repeatsUI = 3;
ONBACKARROW:
while(--$repeats > 0){
# we assume the app is open and at the foreground
# get the UI
$outfile = defined($outbase) ? $outbase.'_main.xml' : undef;
do {
$ui = $self->mother->dump_current_screen_ui({'filename'=>$outfile});
usleep(0.75);
} while( ($repeatsUI-- > 0) && ! defined($ui) );
if( ! defined $ui ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to dump the UI, call to ".'dump_current_screen_ui()'." has failed after a number of repeats. I am not sure what the problem is, most likely a race conditio...
$dom = $ui->{'XML::LibXML'};
$xc = $ui->{'XML::LibXML::XPathContext'};
$asel = '//node'
. '['
. ' matches(@content-desc,\'navigate\s+up\',"i")'
. ' and matches(@class,\'ImageButton\',"i")'
. ' and @package="com.viber.voip"'
. ' and matches(@bounds,\'^\[\',"i")'
. ']'
;
@nodes = $xc->findnodes($asel);
$N = scalar @nodes;
if( $N == 0 ){
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : no nodes matching this XPath selector (for getting the 'back-arrow' icon, hopefully, this means we reached home-page of the app): ${asel}") }
last ONBACKARROW;
} elsif( $N > 1 ){ $log->error("--begin matched nodes:\n".join("\n", @nodes)."\n--end nodes matched.\n\n${whoami} (via $parent), line ".__LINE__." : error, matched more than one node (see above) with this XPath selector (for getting the 'Chats' ico...
$node = $nodes[0];
# click the 'back-arrow' at the top
$boundstr = $node->getAttribute('bounds');
if( ! defined $boundstr ){ $log->error("${node}\n\n${whoami} (via $parent), line ".__LINE__." : error, above node does not have attribute 'bounds'."); return 1 }
if( $boundstr !~ /\[\s*(\d+)\s*,\s*(\d+)\]\s*\[\s*(\d+)\s*,\s*(\d+)\]/ ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to parse bounds string: '$boundstr'."); return 1 }
$bounds = [[$1,$2],[$3,$4]];
# click it
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : clicking the back-arrow ...") }
if( $self->mother->tap({'bounds' => $bounds}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to tap on $bounds."); return 1 }
usleep(0.75);
}
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : hopefully we are now at the very first screen of the app.") }
return 0 # success
}
# "type" the specified message into
# the message will be sanitised in input_text(), spaces will be replaced with %s
# unicode is not supported
sub send_message {
my ($self, $params) = @_;
my $parent = ( caller(1) )[3] || "N/A";
my $whoami = ( caller(0) )[3];
my $log = $self->log();
my $verbosity = $self->verbosity();
my ($recipient, $message, $outbase, $outfile);
if( ! exists($params->{'recipient'}) || ! defined($recipient=$params->{'recipient'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'recipient' was not specified."); return undef }
if( ! exists($params->{'message'}) || ! defined($message=$params->{'message'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'message' was not specified."); return undef }
# for debugging purposes, save each UI we get here
$outbase = exists($params->{'outbase'}) ? $params->{'outbase'} : undef;
# do everything except clicking the send button
my $mock = exists($params->{'mock'}) ? $params->{'mock'} : 0;
my ($dom, $xc, $asel, @nodes, $N, $node, $boundstr, $bounds);
if( $self->navigate_to_viber_home_activity({'outbase'=>$outbase}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, call to ".'navigate_to_viber_home_activity()'." has failed."); return undef }
usleep(1.5);
# we assume the app is open and at the foreground
# get the UI
$outfile = defined($outbase) ? $outbase.'_main.xml' : undef;
# there is always the chance you get 'Is taking too long'...
# and all will fail TODO!
my $ui;
my $repeatsUI = 3;
do {
if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : calling ".'dump_current_screen_ui()'." for at repeat $repeatsUI ...") }
$ui = $self->mother->dump_current_screen_ui({'filename'=>$outfile});
usleep(0.75);
} while( ($repeatsUI-- > 0) && (! defined($ui)) );
if( ! defined $ui ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to dump the UI, call to ".'dump_current_screen_ui()'." has failed after a number of repeats. I am not sure what the problem is, most likely a race condition...
$dom = $ui->{'XML::LibXML'};
$asel = '//node[@text="Chats" and @resource-id="com.viber.voip:id/bottomBarItemTitle"]';
@nodes = $dom->findnodes($asel);
$N = scalar @nodes;
if( $N == 0 ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to find any nodes matching this XPath selector (for getting the 'Chats' icon): ${asel}"); return undef }
elsif( $N > 1 ){ $log->error("--begin matched nodes:\n".join("\n", @nodes)."\n--end nodes matched.\n\n${whoami} (via $parent), line ".__LINE__." : error, matched more than one node (see above) with this XPath selector (for getting the 'Chats' icon):...
$node = $nodes[0];
# click the 'Chats' at the bottom
$boundstr = $node->getAttribute('bounds');
if( ! defined $boundstr ){ $log->error("${node}\n\n${whoami} (via $parent), line ".__LINE__." : error, above node does not have attribute 'bounds'."); return undef }
if( $boundstr !~ /\[\s*(\d+)\s*,\s*(\d+)\]\s*\[\s*(\d+)\s*,\s*(\d+)\]/ ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to parse bounds string: '$boundstr'."); return undef }
$bounds = [[$1,$2],[$3,$4]];
# click it
if( $self->mother->tap({'bounds' => $bounds}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to tap on $bounds."); return undef }
sleep(1);
# now we are on the chats screen, in the centre pane there are all our contacts
# get the ui for this screen
$outfile = defined($outbase) ? $outbase.'_chats.xml' : undef;
$ui = $self->mother->dump_current_screen_ui({'filename'=>$outfile});
$dom = $ui->{'XML::LibXML'};
$xc = $ui->{'XML::LibXML::XPathContext'};
$asel = '//node'
. '['
. '@text'
. ' and matches(@text,\''.$params->{'recipient'}.'\',"i")'
. ' and @resource-id="com.viber.voip:id/from"'
. ']'
;
@nodes = $xc->findnodes($asel);
$N = scalar @nodes;
if( $N == 0 ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to find any nodes matching this XPath selector (for getting the recipient (".$params->{'recipient'}.") from the contacts on the central pane: ${asel}"); return un...
elsif( $N > 1 ){ $log->error("--begin matched nodes:\n".join("\n", @nodes)."\n--end nodes matched.\n\n${whoami} (via $parent), line ".__LINE__." : error, matched more than one node (see above) with this XPath selector (for getting the recipient (".$...
$node = $nodes[0];
# click the Recipient contact name at the bottom
$boundstr = $node->getAttribute('bounds');
if( ! defined $boundstr ){ $log->error("${node}\n\n${whoami} (via $parent), line ".__LINE__." : error, above node does not have attribute 'bounds'."); return undef }
if( $boundstr !~ /\[\s*(\d+)\s*,\s*(\d+)\]\s*\[\s*(\d+)\s*,\s*(\d+)\]/ ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to parse bounds string: '$boundstr'."); return undef }
$bounds = [[$1,$2],[$3,$4]];
# click it
if( $self->mother->tap({'bounds' => $bounds}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to tap on $bounds."); return undef }
usleep(1.5);
# Put the text into the text-edit Message... (note: ... is unicode ellipses something)
# get the UI
$outfile = defined($outbase) ? $outbase.'_chat.xml' : undef;
$ui = $self->mother->dump_current_screen_ui({'filename'=>$outfile});
$dom = $ui->{'XML::LibXML'};
$xc = $ui->{'XML::LibXML::XPathContext'};
$asel = '//node'
. '['
. ' matches(@class,\'EditText$\',"i")'
. ' and @resource-id="com.viber.voip:id/send_text"'
. ' and @package="com.viber.voip"'
. ']'
;
@nodes = $xc->findnodes($asel);
$N = scalar @nodes;
if( $N == 0 ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to find any nodes matching this XPath selector (for getting the specified recipient (".$params->{'recipient'}."): ${asel}"); return undef }
elsif( $N > 1 ){ $log->error("--begin matched nodes:\n".join("\n", @nodes)."\n--end nodes matched.\n\n${whoami} (via $parent), line ".__LINE__." : error, matched more than one node (see above) with this XPath selector (for getting the specified reci...
$node = $nodes[0];
$boundstr = $node->getAttribute('bounds');
if( ! defined $boundstr ){ $log->error("${node}\n\n${whoami} (via $parent), line ".__LINE__." : error, above node does not have attribute 'bounds'."); return undef }
if( $boundstr !~ /\[\s*(\d+)\s*,\s*(\d+)\]\s*\[\s*(\d+)\s*,\s*(\d+)\]/ ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to parse bounds string: '$boundstr'."); return undef }
$bounds = [[$1,$2],[$3,$4]];
# add the text in the node
# the message will be sanitised in input_text(), spaces will be replaced with %s
# unicode is not supported
# TODO: perhaps warn about unicode in message?
if( $self->mother->input_text({'bounds' => $bounds, 'text' => $message}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to input text on ${boundstr}. Note that there may be a maximum length for the message. Your message w...
sleep(1);
# and send!
# on the same XML we search for the send button
$asel = '//node'
. '['
. ' matches(@class,\'FrameLayout$\',"i")'
. ' and @resource-id="com.viber.voip:id/btn_send"'
. ' and @package="com.viber.voip"'
. ']'
;
@nodes = $xc->findnodes($asel);
$N = scalar @nodes;
if( $N == 0 ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to find any nodes matching this XPath selector (for getting the recipient (".$params->{'recipient'}.") from the contacts on the central pane: ${asel}"); return un...
elsif( $N > 1 ){ $log->error("--begin matched nodes:\n".join("\n", @nodes)."\n--end nodes matched.\n\n${whoami} (via $parent), line ".__LINE__." : error, matched more than one node (see above) with this XPath selector (for getting the recipient (".$...
$node = $nodes[0];
# click the Send button
$boundstr = $node->getAttribute('bounds');
if( ! defined $boundstr ){ $log->error("${node}\n\n${whoami} (via $parent), line ".__LINE__." : error, above node does not have attribute 'bounds'."); return undef }
if( $boundstr !~ /\[\s*(\d+)\s*,\s*(\d+)\]\s*\[\s*(\d+)\s*,\s*(\d+)\]/ ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to parse bounds string: '$boundstr'."); return undef }
$bounds = [[$1,$2],[$3,$4]];
# I know it does not support sending unicode to a textbox but ...
# TODO: check with a unicode recipient handle to see if this works
if( $verbosity > 0 ){ $log->info("To: '".Encode::decode_utf8($recipient)."'\nMessage:\n".Encode::decode_utf8($message)."\n--end message.\n${whoami} (via $parent), line ".__LINE__." : about to send the above message ...") }
if( $mock == 0 ){
# click it if not mocking
if( $self->mother->tap({'bounds' => $bounds}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, failed to tap on $bounds."); return undef }
} else {
$log->warn("${whoami} (via $parent), line ".__LINE__." : The 'Send' button was not clicked because mock is ON.")
}
usleep(0.8);
return {};
}
# only pod below
=pod
=encoding utf8
=head1 NAME
Android::ElectricSheep::Automator::Plugins::Apps::Viber - Control the Viber app from your desktop via the ElectricSheep Automator
=head1 VERSION
Version 0.09
=head1 WARNING
Current distribution is extremely alpha. API may change.
=head1 SYNOPSIS
An L<Android::ElectricSheep::Automator> plugin which
interacts with the Viber app from the desktop.
use Android::ElectricSheep::Automator::Plugins::Apps::Viber;
my $viber = Android::ElectricSheep::Automator::Plugins::Apps::Viber->new({
'configfile' => $configfile,
'verbosity' => 1,
# we already have a device connected and ready to control
'device-is-connected' => 1,
});
# go to home screen to start fresh
$plugobj->mother->home_screen();
# open the viber app
$plugobj->open_app() or die
# is the app running now?
$plugobj->is_app_running() or die
( run in 0.928 second using v1.01-cache-2.11-cpan-13bb782fe5a )