Android-ElectricSheep-Automator

 view release on metacpan or  search on metacpan

lib/Android/ElectricSheep/Automator.pm  view on Meta::CPAN


# Inputs parameters:
#   'package' : required package name as a SCALAR (for an exact search)
#     or a regex (qr//) object for regex search including case-insensitive.
#    'activity' : optional activity name to start additionally to the app/package name.
#     If not present, we will try to find the MAIN activities of the package via
#     AppProperties. There could be several MAIN activities and there are heuristics
#     to pick one. See AppProperties::enquire().
#     The spec must yield exactly 1 match, it will complain if more than 1 matches found.
#   'force-reload-apps-list' => 0,1 : optionally call find_installed_apps() if > 0
#     but restricted only to the packages match 'package' NOT ALL.
#   'lazy' => 0,1 : pass this lazy value to the find_installed_apps()
#     and be lazy (i.e. without enquiring on each app's specifics
#     and creating an AppProperties object) if ==1
#     or not be lazy if ==0 ...
#     ... (which means an AppProperties object is created for each found package)
#     Default is force-reload-apps-list=>0
#     THIS APPLIES ONLY TO THE MATCHED 'package'
# On success it returns a hash of {appname => appproperties} of the opened app
#    (which will be created if not existing). It may return {} if no match
#    was found for the specified 'package' name.
# On failure it returns undef
# it needs that connect_device() to have been called prior to this call
sub open_app {
	my ($self, $params) = @_;
	$params //= {};

	my $parent = ( caller(1) )[3] || "N/A";
	my $whoami = ( caller(0) )[3];
	my $log = $self->log();
	my $verbosity = $self->verbosity();

	if( ! $self->is_device_connected() ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, you need to connect a device to the desktop and ALSO explicitly call ".'connect_device()'." before calling this."); return undef }

	my ($package);
	if( ! exists($params->{'package'}) || ! defined($package=$params->{'package'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'package' is required."); return undef }
	if( (ref($package)ne'') && (ref($package)ne'Regexp') ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, the type of input parameter 'package' must be a scalar string (the package name) or a Regexp object (compiled regex via ".'qr//'...

	# optional activity, else we will see if we find one
	my $activity = (exists($params->{'activity'}) && defined($params->{'activity'})) ? $params->{'activity'} : undef;

	my $force_reload = (exists($params->{'force-reload-apps-list'}) && defined($params->{'force-reload-apps-list'})) ? $params->{'force-reload-apps-list'} : 0;
	my $lazy = (exists($params->{'lazy'}) && defined($params->{'lazy'})) ? $params->{'lazy'} : 1;

	my $apps = $self->apps();
	if( (0 == scalar(keys %$apps))
	 || ($force_reload>0)
	){
		my $fpars = {
			'packages' => $package,
			'force-reload-apps-list' => $force_reload,
			'lazy' => $lazy
		};
		if( ! defined($apps=$self->find_installed_apps($fpars)) ){ $log->error(perl2dump($fpars)."${whoami} (via $parent), line ".__LINE__." : error, failed to load list of installed apps, call to ".'find_installed_apps()'." has failed with above parameter...
		if( 0 == scalar(keys %$apps) ){
			$log->error("${whoami} (via $parent), line ".__LINE__." : error, there are no installed apps, even after enquiring them. The target device has no apps installed. Weird.");
			return undef
		}
	}

	# by now we are sure we have the list of installed apps updated
	# but it is likely that there is no AppProperties object for each
	# app in the list, but we need it, so make a search and if
	# AppProperties is undef, then we need to call find_installed_apps() again.
	my $searchres = $self->search_app({
		'package' => $package,
		'force-reload-apps-list' => 0,
	});
	if( ! defined $searchres ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, call to ".'search_app()'." has failed for this search term (package) : ${package}"); return undef }
	my $num_searchres = scalar keys %$searchres;
	if( $num_searchres == 0 ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, no app was found for this search term (package) : ${package}"); return undef }
	elsif( $num_searchres > 1 ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, more than one app was found for this search term (package) : ${package} . Apps found: '".join("', '", sort keys %$searchres)."'."); return undef }
	# only 1 tupple in the hash, get it:
	my ($found_app_name, $found_app_properties) = %$searchres;

	if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : app to open has been matched to '${found_app_name}'.") }

	if( ! defined $found_app_properties ){
		# the app is there in the list but it does not have AppProperties yet.
		# So get just this one package and non-lazily because we need the AppProperties object:
		my $fpars = {
			'force-reload-apps-list' => 0,
			'lazy' => 1, # << lazy for all other packages except our 'package'
			'packages' => $found_app_name,
		};
		if( ! defined $self->find_installed_apps($fpars) ){ $log->error(perl2dump($fpars)."${whoami} (via $parent), line ".__LINE__." : error, failed to load list of installed apps, call to ".'find_installed_apps()'." has failed with above parameters."); r...
		$apps = $self->apps();
		$found_app_properties = $apps->{$found_app_name};
	}

	if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : opening app '".$found_app_properties->get('packageName')."' ...") }

	my ($MainActivity, $fact, @cmd);
	if( ! defined($MainActivity=$found_app_properties->get('MainActivity'))
	 || ! exists($MainActivity->{'name-fully-qualified'})
	 || ! defined($fact=$MainActivity->{'name-fully-qualified'})
	){
		$log->warn("${whoami} (via $parent), line ".__LINE__." : error, above app (package '${package}') does not contain a 'MainActivity' entry. Launching without it ...");
		$fact = $found_app_properties->get('packageName');
		@cmd = ('am', 'start', $fact);
	} else {
		@cmd = ('am', 'start', '-n', $fact);
	}

	# open it
	if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : sending command to adb: @cmd") }
	my $res = $self->adb->shell(@cmd);
	if( ! defined $res ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, got undefined result, most likely shell command did not run at all, this should not be happening."); return unde...
	if( $res->[0] != 0 ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, with:\nSTDOUT:\n".$res->[1]."\n\nSTDERR:\n".$res->[2]."\nEND."); return undef }

	# we are returning a hash of name=>appproperties
	# but because we allow 1 match only, this hash will only contain 1 item
	# but it will be easier to allow more apps in the future if
	# we return a hash here
	return { $found_app_name => $found_app_properties };
}

# Inputs parameters:
#   'package' => required package name as a SCALAR (for an exact search)
#     or a regex (qr//) object for regex search including case-insensitive.
#     The spec must yield exactly 1 match, it will complain if more than 1 matches found.
#   'force-reload-apps-list' => 0,1 : optionally call find_installed_apps() if > 0
#     but restricted only to the packages match 'package' NOT ALL.
#   'lazy' => 0,1 : pass this lazy value to the find_installed_apps()
#     and be lazy (i.e. without enquiring on each app's specifics
#     and creating an AppProperties object) if ==1
#     or not be lazy if ==0 ...
#     ... (which means an AppProperties object is created for each found package)
#     Default is force-reload-apps-list=>0
#     THIS APPLIES ONLY TO THE MATCHED 'package'
# On success it returns the a hash of {appname => appproperties} of the closed app
#    (which will be created if not existing). It may return {} if no match.
# On failure it returns undef
# it needs that connect_device() to have been called prior to this call
sub close_app {
	my ($self, $params) = @_;
	$params //= {};

	my $parent = ( caller(1) )[3] || "N/A";
	my $whoami = ( caller(0) )[3];
	my $log = $self->log();
	my $verbosity = $self->verbosity();

	if( ! $self->is_device_connected() ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, you need to connect a device to the desktop and ALSO explicitly call ".'connect_device()'." before calling this."); return undef }

	my ($package);
	if( ! exists($params->{'package'}) || ! defined($package=$params->{'package'}) ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, input parameter 'package' is required."); return undef }
	if( (ref($package)ne'') && (ref($package)ne'Regexp') ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, the type of input parameter 'package' must be a scalar string (the package name) or a Regexp object (compiled regex via ".'qr//'...

	# optional activity, else we will see if we find one
	my $activity = (exists($params->{'activity'}) && defined($params->{'activity'})) ? $params->{'activity'} : undef;

	my $force_reload = (exists($params->{'force-reload-apps-list'}) && defined($params->{'force-reload-apps-list'})) ? $params->{'force-reload-apps-list'} : 0;
	my $lazy = (exists($params->{'lazy'}) && defined($params->{'lazy'})) ? $params->{'lazy'} : 1;

	my $apps = $self->apps();
	if( (0 == scalar(keys %$apps))
	 || ($force_reload>0)
	){
		my $fpars = {
			'packages' => $package,
			'force-reload-apps-list' => $force_reload,
			'lazy' => $lazy
		};
		if( ! defined($apps=$self->find_installed_apps($fpars)) ){ $log->error(perl2dump($fpars)."${whoami} (via $parent), line ".__LINE__." : error, failed to load list of installed apps, call to ".'find_installed_apps()'." has failed with above parameter...
		if( 0 == scalar(keys %$apps) ){
			$log->error("${whoami} (via $parent), line ".__LINE__." : error, there are no installed apps, even after enquiring them. The target device has no apps installed. Weird.");
			return undef
		}
	}

	# by now we are sure we have the list of installed apps updated
	# but it is likely that there is no AppProperties object for each
	# app in the list, but we need it, so make a search and if
	# AppProperties is undef, then we need to call find_installed_apps() again.
	my $searchres = $self->search_app({
		'package' => $package,
		'force-reload-apps-list' => 0,
	});
	if( ! defined $searchres ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, call to ".'search_app()'." has failed for this search term (package) : ${package}"); return undef }
	my $num_searchres = scalar keys %$searchres;
	if( $num_searchres == 0 ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, no app was found for this search term (package) : ${package}"); return {} }
	elsif( $num_searchres > 1 ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, more than one app was found for this search term (package) : ${package} . Apps found: '".join("', '", sort keys %$searchres)."'."); return undef }
	# only 1 tupple in the hash, get it:
	my ($found_app_name, $found_app_properties) = %$searchres;

	if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : app to open has been matched to '${found_app_name}'.") }

	if( ! defined $found_app_properties ){
		# the app is there in the list but it does not have AppProperties yet.
		# So get just this one package and non-lazily because we need the AppProperties object:
		my $fpars = {
			'force-reload-apps-list' => 0,
			'lazy' => 1, # << lazy for all other packages except our 'package'
			'packages' => $found_app_name,
		};
		if( ! defined $self->find_installed_apps($fpars) ){ $log->error(perl2dump($fpars)."${whoami} (via $parent), line ".__LINE__." : error, failed to load list of installed apps, call to ".'find_installed_apps()'." has failed with above parameters."); r...
		$apps = $self->apps();
		$found_app_properties = $apps->{$found_app_name};
	}

	if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : closing app '".$found_app_properties->get('packageName')."' ...") }

	# adb shell am force-stop com.my.app
	my @cmd = ('am', 'force-stop', $found_app_properties->get('packageName'));
	if( $verbosity > 0 ){ $log->info("${whoami} (via $parent), line ".__LINE__." : sending command to adb: @cmd") }
	my $res = $self->adb->shell(@cmd);
	if( ! defined $res ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, got undefined result, most likely shell command did not run at all, this should not be happening."); return unde...
	if( $res->[0] != 0 ){ $log->error(join(" ", @cmd)."\n${whoami} (via $parent), line ".__LINE__." : error, above shell command has failed, with:\nSTDOUT:\n".$res->[1]."\n\nSTDERR:\n".$res->[2]."\nEND."); return undef }

	# we are returning a hash of name=>appproperties
	# but because we allow 1 match only, this hash will only contain 1 item
	# but it will be easier to allow more apps in the future if
	# we return a hash here
	return { $found_app_name => $found_app_properties };
}

# returns pid (as a non-negative integer) of the specified app by its exact name
# or -1 if nothing was matched in the process table.
# on error it returns undef
# Note: if you do not know the exact app name e.g. com.viber.voip
# use pgrep()
sub pidof {
	my ($self, $params) = @_;
	$params //= {};

	my $parent = ( caller(1) )[3] || "N/A";
	my $whoami = ( caller(0) )[3];
	my $log = $self->log();
	my $verbosity = $self->verbosity();

	if( ! $self->is_device_connected() ){ $log->error("${whoami} (via $parent), line ".__LINE__." : error, you need to connect a device to the desktop and ALSO explicitly call ".'connect_device()'." before calling this."); return undef }



( run in 0.451 second using v1.01-cache-2.11-cpan-df04353d9ac )