def is_playing(self): vc = ViewClient(self.device, self.serialno) play_pause_header_key = GoogleMusicApp.PLAY_PAUSE_HEADER_KEY play_pause_btn_view = [ v for v in vc.getViewsById().values() if v.getId() == play_pause_header_key ] play_pause_btn_view = play_pause_btn_view[0] if len( play_pause_btn_view) > 0 else None if play_pause_btn_view: return self.check_play_status( utf8str(play_pause_btn_view.getContentDescription())) ctrl_panel_play_pause_key = GoogleMusicApp.CONTROL_PANEL_PLAY_PAUSE_KEY ctrl_panel_play_pause_view = [ v for v in vc.getViewsById().values() if v.getId() == ctrl_panel_play_pause_key ] ctrl_panel_play_pause_view = ctrl_panel_play_pause_view[0] if len( ctrl_panel_play_pause_view) > 0 else None if ctrl_panel_play_pause_view: return self.check_play_status( utf8str(ctrl_panel_play_pause_view.getContentDescription())) return False
def main(): device, serialno = ViewClient.connectToDeviceOrExit(serialno=None) vc = ViewClient(device, serialno) views_dict = vc.getViewsById() print( "-------- Show the detail in the returned by the API ViewClient.getViewsById() --------" ) for key, value in views_dict.items(): print("{}:\n{}\n".format(key, unicode(value).encode("UTF-8"))) views = filter(lambda v: len(v.getId()) > 0, views_dict.values()) id_view_dict = {} for v in views: if v.getId() in id_view_dict.keys(): id_view_dict[v.getId()].append(v) else: id_view_dict[v.getId()] = [v] print("\n") print("-------- Print the id-to-view pairs --------") for key, value in id_view_dict.items(): for each in value: print("{}:\n{}\n".format(key, unicode(each).encode("UTF-8"))) vc.traverse() pass
def _fetch_songs(self): vc = ViewClient(self.device, self.serialno, autodump=False) songs = {} drag_up_count = 0 li_title_key = GoogleMusicApp.LI_TITLE_KEY while True: song_props = self._fetch_songs_on_current_screen(vc=vc) song_props = filter(lambda prop: not prop[0] in songs.keys(), song_props) if len(song_props) == 0: break for name, duration in song_props: v = [v for v in vc.getViewsById().values() if v.getId() == li_title_key and utf8str(v.getText()) == name] if len(v) != 1: self.push_dump("in _fetch_songs, got multiple songs with the same name '{}'".format(name)) v = v[0] if len(v) > 0 else None songs[name] = { "duration": duration, "drag_up_count": drag_up_count, "position": v.getCenter() if v else (-1, -1) } self._drag_up() drag_up_count += 1 for name, info in songs.items(): self.push_dump("{}: {}".format(name, info)) return songs
def get_boomsound_id(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("text=HTC BoomSound") > 0: return vc, key return None, None
def get_play_pause_id(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("id/play_controls") > 0: return vc, key return None, None
def get_callDuration_id(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("id/callDuration") > 0: return True return False
def get_menu_id(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("content-desc=More options") > 0: return vc, key return None, None
def get_rename_edit_id(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("id/textName") > 0: return vc, key return None, None
def get_normal_id(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("text=Normal quality (AMR_NB)") > 0: return vc, key return None, None
def get_flac_id(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("text=High res quality (FLAC)") > 0: return vc, key return None, None
def get_ArtPager_id(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("id/art_pager") > 0: return vc, key return None
def get_fragment_list_view(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("id/fragment_list") > 0: view = vc.findViewByIdOrRaise(key) return view return None
def get_callStateLable_id(device, serialno): vc = ViewClient(device, serialno) view_dict = vc.getViewsById() for key, value in view_dict.items(): strTemp = unicode(value).encode("UTF-8") if strTemp.find("text=DIALING") > 0 and strTemp.find( "id/callStateLable") > 0: return True return False
def main(output_path="out.png"): device, serialno = ViewClient.connectToDeviceOrExit(serialno=None) vc = ViewClient(device, serialno) img = np.array(device.takeSnapshot()) plt.imshow(img) views_dict = vc.getViewsById() # clickables = filter(lambda v: v.getClickable(), views_dict.values()) # views = clickables views = views_dict.values() for v in views: min_c, max_c = v.getBounds() draw_rect(min_c, max_c, style="r--", linewidth=1) plt.annotate(v.getId().split("/")[-1] if len(v.getId()) > 0 else "\"empty\"", xy=v.getCenter(), horizontalalignment="center", verticalalignment="center", color="b", size=2.5) plt.gca().get_xaxis().set_visible(False) plt.gca().get_yaxis().set_visible(False) plt.savefig(output_path, bbox_inches="tight", pad_inches=0, dpi=300) pass
def testAppFromTestcase(apkPath, testCaseFile, avdSerialno="", waitInterval=1, takeSnapshots=False, allowCrashes=False, uninstallApp=True): """ Tests an app according to a specific test case loaded from a JSON file generated by a previous test :param apkPath: The path to the APK to test :type apkPath: str :param testCaseFile: The path to the test case file from whence the actions are loaded :type testCaseFile: str :param avdSerialno: The serial number of the Android virtual device (in case multiple devices are running simultaneously) :type avdSerialno: str :param waitInterval: The time (in seconds) to wait between actions :type waitInterval: int :param takeSnapshots: Whether to take snapshots of the device screen after every performed action (default: False) :type takeSnapshots: bool :param allowCrashes: Whether to allow the app under test to crash. If (by default) False, the app will be re-started and re-tested. :type allowCrashes: bool :param uninstallApp: Whether to uninstall the app under test before returning :type uninstallApp: bool :return: A bool indicating the success/failure of the test """ try: # 1. Connect to the virtual device prettyPrint("Connecting to device", "debug") if avdSerialno != "": vc = ViewClient(*ViewClient.connectToDeviceOrExit( ignoreversioncheck=True, verbose=True, serialno=avdSerialno)) else: vc = ViewClient(*ViewClient.connectToDeviceOrExit( ignoreversioncheck=True, verbose=True)) # 1.a. Analyzing app prettyPrint("Analyzing app using \"androguard\"", "debug") apk, dx, vm = analyzeAPK(apkPath) appComponents = extractAppComponents(apk) # 2. Install package and configure Introspy (if requested) prettyPrint("Installing package \"%s\"" % apkPath, "debug") subprocess.call([vc.adb, "-s", avdSerialno, "install", "-r", apkPath]) # 3. Load and parse test case file prettyPrint("Loading the test case file \"%s\"" % testCaseFile, "debug") if not os.path.exists(testCaseFile): prettyPrint("Could not find the test case file", "error") return False content = json.loads(open(testCaseFile).read()) if len(content["events"]) < 1: prettyPrint("Could not retrieve events to run", "error") # 4. Iterate over events and execute them tapEvents = [ "Button", "CheckBox", "RadioButton", "Switch", "ToggleButton" ] touchEvents = ["longtouch", "touch"] textEvents = ["EditText"] swipeEvents = ["swipeleft", "swiperight"] pressEvents = ["press"] for e in content["events"]: # 4.1. Parse and execute event if e["type"] == "activity": try: prettyPrint("Starting activity \"%s\"" % e["id"], "debug") vc.device.startActivity(e["id"]) except exceptions.RuntimeError as rte: prettyPrint("Unable to start activity \"%s\"" % e["id"], "warning") elif e["type"] == "broadcast": prettyPrint("Broadcasting intent action: %s" % e["intent"], "debug") vc.device.shell("am broadcast -a %s" % e["intent"]) elif e["type"] in tapEvents: prettyPrint( "Tapping %s at (%s, %s)" % (e["type"], e["x"], e["y"]), "debug") vc.touch(int(e["x"]), int(e["y"])) elif e["type"] in textEvents: prettyPrint( "Writing \"%s\" to EditText field \"%s\"" % (e["text"], e["id"]), "debug") allviews = vc.getViewsById() if len(allviews) < 1 or e["id"] not in allviews.keys(): prettyPrint( "Could not find EditText with id \"%s\". Skipping" % e["id"], "warning") else: allviews[e["id"]].setText(e["text"]) elif e["type"] in swipeEvents: prettyPrint( "Swiping screen from (%s, %s) to (%s, %s)" % (e["x"], e["y"], e["xd"], e["yd"]), "debug") vc.swipe(int(e["x"]), int(e["y"]), int(e["xd"]), int(e["yd"])) elif e["type"] in pressEvents: prettyPrint("Pressing \"%s\"" % e["key"], "debug") vc.device.press(e["key"]) elif e["type"].lower().find("touch") != -1: if e["type"] == "longtouch": prettyPrint("Long touching at (%s,%s)" % (e["x"], e["y"]), "debug") vc.longTouch(int(e["x"]), int(e["y"])) else: prettyPrint("Touching at (%s,%s)" % (e["x"], e["y"]), "debug") vc.touch(int(e["x"]), int(e["y"])) # 4.2. Wait for [waitInterval] seconds and take a snapshot if instructed to if waitInterval > 0: prettyPrint("Waiting for %s seconds" % waitInterval, "debug") time.sleep(waitInterval) if takeSnapshots: prettyPrint("Taking snapshot", "debug") snapshot = vc.device.takeSnapshot() snapshot.save( "%s_%s.png" % (appComponents["package_name"], str(int(time.time())))) # 4.3. Check whether the performed action crashed or stopped (sent to background) the app if _appCrashed(vc, avdSerialno): if not allowCrashes: prettyPrint( "The previous action(s) caused the app to crash. Exiting", "warning") if uninstallApp: prettyPrint("Uninstalling app \"%s\"" % appComponents["package_name"]) subprocess.call([ vc.adb, "-s", avdSerialno, "uninstall", appComponents["package_name"] ]) return False prettyPrint( "The previous action(s) caused the app to crash. Restarting", "warning") vc.device.startActivity("%s/%s" % (appComponents["package_name"], appComponents["main_activity"])) time.sleep( waitInterval) # Give time for the main activity to start elif _appStopped(vc, appComponents, avdSerialno): prettyPrint( "The previous action(s) stopped the app. Restarting", "warning") vc.device.startActivity("%s/%s" % (appComponents["package_name"], appComponents["main_activity"])) time.sleep( waitInterval) # Give time for the main activity to start # 6. Uninstalling app under test if uninstallApp: prettyPrint("Uninstalling app \"%s\"" % appComponents["package_name"]) subprocess.call([ vc.adb, "-s", avdSerialno, "uninstall", appComponents["package_name"] ]) except Exception as e: prettyPrintError(e) return False return True
class SSRDumpListenerThread(threading.Thread): class State(object): IDLE = "idle" BEING_DUMPED = "being dumped" DUMPED = "has been dumped" def __init__(self, device, serialno): super(SSRDumpListener.SSRDumpListenerThread, self).__init__() self.daemon = True self.stoprequest = threading.Event() self.lock = threading.Lock() self.device = device self.serialno = serialno self.vc = ViewClient(device, serialno, autodump=False) self.state = SSRDumpListener.SSRDumpListenerThread.State.IDLE def join(self, timeout=None): self.stoprequest.set() super(SSRDumpListener.SSRDumpListenerThread, self).join(timeout) def run(self): pattern = re.compile("Window{.+?}") while not self.stoprequest.isSet(): try: win_dumpsys, _ = subprocess.Popen( \ "adb -s {} shell dumpsys window windows | grep \"Window #\"".format(self.serialno), \ shell=True, stdout=subprocess.PIPE).communicate() win_info_strs = map( lambda s: pattern.search(s.strip()).group(0), win_dumpsys.splitlines()) win_info_strs = [ s[7:-1] for s in win_info_strs if len(s) > 7 ] self.win_info = dict([ (ss[2], ss[0]) for ss in map(lambda s: s.split(), win_info_strs) ]) if SSRDumpListener.FILTER in self.win_info.keys(): self.vc.dump( window=self.win_info[SSRDumpListener.FILTER], sleep=0) views = dict([(v.getId(), v) for v in self.vc.getViewsById().values() if len(v.getId()) > 0]) if SSRDumpListener.MSG_VIEW_ID in views.keys(): msg = views[SSRDumpListener.MSG_VIEW_ID].getText() if SSRDumpListener.RAMDUMP_COMPLETED_KEYMSG in msg: self.state = SSRDumpListener.SSRDumpListenerThread.State.DUMPED else: self.state = SSRDumpListener.SSRDumpListenerThread.State.BEING_DUMPED else: self.state = SSRDumpListener.SSRDumpListenerThread.State.IDLE except: pass time.sleep(SSRDumpListener.PERIOD) def dismiss_dialog(self): if self.state == SSRDumpListener.SSRDumpListenerThread.State.IDLE: return self.vc.dump(window=self.win_info[SSRDumpListener.FILTER], sleep=0) views = dict([(v.getId(), v) for v in self.vc.getViewsById().values() if len(v.getId()) > 0]) if SSRDumpListener.BTN_ID in views.keys(): x, y = views[SSRDumpListener.BTN_ID].getCenter() self.device.touch(x, y) self.state = SSRDumpListener.SSRDumpListenerThread.State.IDLE
def walk_through(self): if not self.to_top(): Logger.log("GoogleMusicApp", "walk_through failed: unable to go to top activity") self.cache_init = False return False # Get the playcard titles vc = ViewClient(self.device, self.serialno) self.cache_init = True container_key = GoogleMusicApp.CONTAINER_KEY container = [v for v in vc.getViewsById().values() if v.getId() == container_key] container = container[0] if len(container) > 0 else None if container: self.cache["screen-info"] = container.getBounds()[1] self.push_dump("screen-info: {}".format(self.cache["screen-info"])) so = sio.StringIO() vc.traverse(stream=so) lines = so.getvalue().splitlines() play_card_key = GoogleMusicApp.PLAY_CARD_KEY playcards_idices = [idx for idx, line in enumerate(lines) if play_card_key in line] playcards_idices.append(len(lines)) playcards_titles = [] last_idx = playcards_idices[0] li_title_key = GoogleMusicApp.LI_TITLE_KEY for idx in playcards_idices[1:]: li_title_texts = [line for line in lines[last_idx:idx] if li_title_key in line] last_idx = idx if len(li_title_texts) != 1: self.push_dump("li_title_texts has length {}".format(len(li_title_texts))) playcards_titles.append(utf8str(li_title_texts[0].split(li_title_key)[-1].strip())) self.push_dump("playcards_titles.append('{}')".format(playcards_titles[-1])) # Get the track list of each playcard views = [v for v in vc.getViewsById().values() if v.getId() == li_title_key and utf8str(v.getText()) in playcards_titles] self.cache["playcard"] = dict( \ map(lambda v: (utf8str(v.getText()), { "position": v.getCenter() }), views) ) map(lambda v: self.push_dump("view: {}".format(utf8str(v))), views) map(lambda title: self.push_dump("playcard title: '{}'".format(title)), self.cache["playcard"].keys()) if len(views) == 0: self.cache_init = False return False self.cache["shuffle_key"] = playcards_titles[0] self.push_dump("get the shuffle keyword '{}'".format(self.cache["shuffle_key"])) self.touch_playcard(self.cache["shuffle_key"]) time.sleep(1) retry_count = 3 while retry_count > 0: vc.dump() play_pause_header_key = GoogleMusicApp.PLAY_PAUSE_HEADER_KEY play_pause_btn_view = [v for v in vc.getViewsById().values() if v.getId() == play_pause_header_key] play_pause_btn_view = play_pause_btn_view[0] if len(play_pause_btn_view) > 0 else None if play_pause_btn_view: play_desc = utf8str(play_pause_btn_view.getContentDescription()) self.check_play_status = lambda desc: desc == play_desc self.cache["play_pause_btn"] = { "position": play_pause_btn_view.getCenter(), "desc_feat": play_desc } art_pager_key = GoogleMusicApp.ART_PAGER_KEY art_pager_view = [v for v in vc.getViewsById().values() if v.getId() == art_pager_key] art_pager_view = art_pager_view[0] if len(art_pager_view) > 0 else None if not art_pager_view: retry_count -= 1 continue self.cache["art_pager_view"] = { "position": art_pager_view.getCenter() } play_pause_btn_view.touch() break else: self.push_dump("cannot find the play/pause button, retry: {}".format(retry_count)) retry_count -= 1 if retry_count == 0: return False for li_title in self.cache["playcard"].keys(): if li_title == self.cache["shuffle_key"]: continue self.push_dump("now fetching information in the playcard '{}'".format(li_title)) if self.touch_playcard(li_title=li_title): time.sleep(1) self.cache["playcard"][li_title]["songs"] = self._fetch_songs() self.to_top() # Get the information of the control panel retry_count = 3 while self.get_state() != GoogleMusicApp.State.CONTROL_PANEL and retry_count > 0: self.device.touch(*self.cache["art_pager_view"]["position"]) retry_count -= 1 if retry_count == 0 and self.get_state() != GoogleMusicApp.State.CONTROL_PANEL: self.to_top() time.sleep(5) self.touch_playcard(self.cache["shuffle_key"]) time.sleep(2) self.device.touch(*self.cache["play_pause_btn"]["position"]) time.sleep(2) self.device.touch(*self.cache["art_pager_view"]["position"]) time.sleep(2) if self.get_state() != GoogleMusicApp.State.CONTROL_PANEL: self.push_dump("cannot get the information of the control panel") self.cache_init = False return False def find_view_position(vc, res_id): v = [v for v in vc.getViewsById().values() if v.getId() == res_id] if len(v) == 0: return ((-1, -1), (-1, -1)), (-1, -1) return v[0].getBounds(), v[0].getCenter() vc.dump() progress_bounds, progress_pos = find_view_position(vc, GoogleMusicApp.CONTROL_PANEL_PROGRESS_KEY) self.cache["control_panel"] = { "progress": { "position": progress_pos, "xbounds": [progress_bounds[0][0], progress_bounds[1][0]] }, "prev": { "position": find_view_position(vc, GoogleMusicApp.CONTROL_PANEL_PREV_KEY)[1] }, "next": { "position": find_view_position(vc, GoogleMusicApp.CONTROL_PANEL_NEXT_KEY)[1] }, "play_pause": { "position": find_view_position(vc, GoogleMusicApp.CONTROL_PANEL_PLAY_PAUSE_KEY)[1] } } self.control_panel = GMControlPanel(self) self.push_dump("successfully walked through, now back to top") self.to_top() self.cache_init = True return True