# -*- coding: utf-8 -*-

# ***********************************************************************
# This modue makes only use of standard libraries.
# (Some functions are copied from my own libs, these are marked)
# All imports are listed here at the top.
# Unfortunately it uses some Windows specific libraries.
#
# License : BSD
# ***********************************************************************
import os
import subprocess
import time
import psutil
import wmi
import win32api
import win32con

# ***********************************************************************
_Version_Text = [

[ 0.1 , '27-11-2011', 'Stef Mientki',
'Test Conditions:', (2,),
"""
- First release
""" ]
]
# ***********************************************************************


# ***********************************************************************
# Some parameters that should be adapted to the local situation
# ***********************************************************************
ADB_Path = r'P:\Program Files\Android\android-sdk\platform-tools'
ADB_File = 'adb.exe'
ADB_Prog = os.path.join ( ADB_Path, ADB_File )
Shell_True = True


# ***********************************************************************
# ***********************************************************************
def Get_Process_PID ( process ) :  ## FROM SYSTEM_SUPPORT
  """
name must be the full filename, name + extension
the search is done case-insensitive.
  """
  process = process.lower ()
  for p in psutil.process_iter ():
    if p.name.lower() == process :
      return p.pid
  return -1
# ***********************************************************************

# ***********************************************************************
def Get_PID_from_CommandLine ( Executable, Commandline ) :  ## FROM SYSTEM_SUPPORT
  """
Returns the PID of the process = Executable and
where the commandline contains Commandline
WMI, so function is slow ( may take several seconds)
  """
  c = wmi.WMI ()
  Result = {}
  for process in c.Win32_Process ():
    if ( process.Caption == Executable ) and \
       ( Commandline in process.CommandLine ) :
      return process.ProcessId
# ***********************************************************************

# ***********************************************************************
# ***********************************************************************
def Kill_Process_pid ( pid ) :   ## FROM SYSTEM_SUPPORT
  if os.name != 'nt' :
    pass
  else:
    handle = win32api.OpenProcess(win32con.PROCESS_TERMINATE, 0, pid) #get process handle
    try:
      win32api.TerminateProcess(handle,0) #kill by handle
      win32api.CloseHandle(handle)        #close api
    except:   #the process might already be closed by the user
      pass
# ***********************************************************************


# *************************************************************************
# *************************************************************************
class ADB_Connection ( object ) :
  def __init__ ( self ) :
    """
=====  Attributes  =====
My_ADB = Parent of the ADB-Client, if the ADB-Client was started by me
           or
       = 0, if ADB-Client was started outside this module
           or
       = None, if no ADB-Client available

SmartPhones = list of SmartPhones connected to the host PC

SL4A_Servers = List of tuples ( IP, port ) of public available and usable
               SL4A servers on the SmartPhone

=====  Tests  =====
  + no ADB program available on host
  + no SmartPhone connected, no ADB running
  + no SmartPhone connected, ADB already running
  + SmartPhone connected, no ADB running, no SL4A running
    (SL4A server keeps running afterwards, is that OK ?)
  + SmartPhone connected,    ADB running, no SL4A running
    (if the smartphone has the Server list page open, it doesn't refresh !!)
  + SmartPhone connected, no ADB running,    SL4A running
  + SmartPhone connected,    ADB running,    SL4A running
"""
    self.SmartPhones = []
    self._Start_ADB_Client ()

    # Test if a suitable SL4A-server is running on the smartphone,
    # if not start a new one
    if not ( self._Test_SL4A_Server () ) :
      self._Start_SL4A_Server ()
      time.sleep (2)
      self._Test_SL4A_Server ()

  # *************************************************
  def _Start_ADB_Client ( self ) :
    """
Test if an ADB-Client is running on the host PC,
if not, try to start an ADB-Client.
    """
    # test if there's an ADB-Client running on the host
    PID = Get_Process_PID ( ADB_File )

    # if not already running, start the ADB-Client
    if PID <= 0 :
      ##if not ( File_Exists ( ADB_Prog ) ) :
      if not ( os.path.exists ( ADB_Prog ) ) :
        self.My_ADB = None
      else :
        # to start the ADB-Client,
        # apparently we need to specify some extra parameters like "devices"
        # otherwise the ADB-Client will stop again
        os.chdir ( ADB_Path )
        self.My_ADB = subprocess.Popen( ['adb', 'devices'], shell  = Shell_True )

        # wait till ADB-Client is running
        while not Get_PID_from_CommandLine ( ADB_File, 'fork-server server' ) :
          pass

    else :
      self.My_ADB = 0

    return self.My_ADB


  # *************************************************
  def _Check_SmartPhone ( self ) :
    """
Find all SmartPhones connected through the ADB-Client.
    """
    self.SmartPhones = []
    if self.My_ADB is None :
      return

    os.chdir ( ADB_Path )
    Process = subprocess.Popen( ['adb', 'devices'],
                                cwd    = ADB_Path,
                                stdout = subprocess.PIPE,
                                stderr = subprocess.PIPE,
                                shell  = Shell_True )
    Process.wait ()
    Result = Process.stdout.read().splitlines()
    if len ( Result ) > 1 :
      for line in Result [1:] :
        line = line.strip()
        if line :
          self.SmartPhones.append ( line )
      return True

  # *************************************************
  def _Test_SL4A_Server ( self ) :
    """
Find all running SL4A Servers on the smartphone,
that are public and open.
    """
    self.SL4A_Servers = []
    if self.My_ADB is None :
      return

    # first we've to check if a smartphone is connected
    if not ( self._Check_SmartPhone () ) :
      return self.SL4A_Servers

    os.chdir ( ADB_Path )
    Process = subprocess.Popen( [ 'adb', 'shell', 'netstat' ],
                                cwd    = ADB_Path,
                                stdout = subprocess.PIPE,
                                stderr = subprocess.PIPE,
                                shell  = Shell_True )
    Process.wait ()
    TCPs = Process.stdout.read().splitlines()
    for line in TCPs :
      line = line.strip()
      if line.startswith ( 'tcp6' ) and \
         ( ':::*' in line )         and \
         not ( '127.0.0.1' in line ) :
        parts = line.split ( ':' )
        self.SL4A_Servers.append (( parts[3].strip(), parts[4].strip() ))
    return self.SL4A_Servers

  # *************************************************
  def _Start_SL4A_Server ( self ) :
    """
If an ADB-Client is running,
a new public SL4A server will be created.
    """
    if self.My_ADB is None :
      return

    Cmd  = 'adb shell am start -a com.googlecode.android_scripting.action.LAUNCH_SERVER '
    Cmd += '-n com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher '
    Cmd += '--ez com.googlecode.android_scripting.extra.USE_PUBLIC_IP true'
    subprocess.Popen( Cmd.split(' '), shell  = Shell_True )

  # *************************************************
  def __repr__ ( self ) :
    """
Makes a nice readible string of all attributes, example:
======  ADB parameters  ======
My_ADB       : <subprocess.Popen object at 0x012DEE10>
SmartPhones  : ['0123456789ABCDEF\tdevice']
SL4A-Servers : [('192.168.1.114', '54980')]
    """
    line = '\n======  ADB parameters  ======\n'
    line += 'My_ADB       : ' + str ( self.My_ADB ) + '\n'
    line += 'SmartPhones  : ' + str ( self.SmartPhones ) + '\n'
    line += 'SL4A-Servers : ' + str ( self.SL4A_Servers ) + '\n'
    return line

  # *************************************************
  def Command ( self, Command ) :
    Process = subprocess.Popen( [ ADB_Prog ] + Command.split ( ' ' ),
                                cwd    = ADB_Path,
                                stdout = subprocess.PIPE,
                                stderr = subprocess.PIPE,
                                shell  = Shell_True )

    Process.wait()
    print ']]]]]', Process.stdout.read()


  # *************************************************
  def Close ( self ) :
    """
If the ADB-Client was started from this module,
the ADB-Client will be killed here.
    """
    if self.My_ADB :
      PID = Get_Process_PID ( ADB_File )
      Kill_Process_pid ( PID )
# *************************************************************************


# *************************************************************************
# *************************************************************************
if __name__ == "__main__":
  ADB = ADB_Connection ()
  print ADB

  # Demo if True
  if True :
    # If you want to store the program on the SmartPhone,
    # set next variable to True
    Store_On_Phone = True

    # Create a simple program
    Simple_Program = """
import android
droid = android.Android (( '%s', %s ))
print droid.makeToast ( "Wasn't that easy?")
""" % ( ADB.SL4A_Servers [-1][0], ADB.SL4A_Servers [-1][1] )

    if Store_On_Phone :
      # Store the program in a local file
      Filename = 'temp_sl4a.py'
      fh = open ( Filename, 'w' )
      fh.write ( Simple_Program )
      fh.close ()

      # copy the program to the SmartPhone
      Phone = '/sdcard/sl4a/scripts'
      Command = 'push %s %s' % ( Filename, Phone )
      ADB.Command ( Command )

    # execute the program (this will run the program from the host PC !!)
    exec ( Simple_Program )

  # general testing
  else :
    time.sleep (5)
    ADB.Close()
# *************************************************************************
