/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- / /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; // This file is only loaded on Gonk to manage ADB state Components.utils.import("resource://gre/modules/FileUtils.jsm"); const DEBUG = false; var debug = function(str) { dump("AdbController: " + str + "\n"); } var AdbController = { locked: undefined, remoteDebuggerEnabled: undefined, lockEnabled: undefined, disableAdbTimer: null, disableAdbTimeoutHours: 12, umsActive: false, setLockscreenEnabled: function(value) { this.lockEnabled = value; DEBUG && debug("setLockscreenEnabled = " + this.lockEnabled); this.updateState(); }, setLockscreenState: function(value) { this.locked = value; DEBUG && debug("setLockscreenState = " + this.locked); this.updateState(); }, setRemoteDebuggerState: function(value) { this.remoteDebuggerEnabled = value; DEBUG && debug("setRemoteDebuggerState = " + this.remoteDebuggerEnabled); this.updateState(); }, startDisableAdbTimer: function() { if (this.disableAdbTimer) { this.disableAdbTimer.cancel(); } else { this.disableAdbTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); try { this.disableAdbTimeoutHours = Services.prefs.getIntPref("b2g.adb.timeout-hours"); } catch (e) { // This happens if the pref doesn't exist, in which case // disableAdbTimeoutHours will still be set to the default. } } if (this.disableAdbTimeoutHours <= 0) { DEBUG && debug("Timer to disable ADB not started due to zero timeout"); return; } DEBUG && debug("Starting timer to disable ADB in " + this.disableAdbTimeoutHours + " hours"); let timeoutMilliseconds = this.disableAdbTimeoutHours * 60 * 60 * 1000; this.disableAdbTimer.initWithCallback(this, timeoutMilliseconds, Ci.nsITimer.TYPE_ONE_SHOT); }, stopDisableAdbTimer: function() { DEBUG && debug("Stopping timer to disable ADB"); if (this.disableAdbTimer) { this.disableAdbTimer.cancel(); this.disableAdbTimer = null; } }, notify: function(aTimer) { if (aTimer == this.disableAdbTimer) { this.disableAdbTimer = null; // The following dump will be the last thing that shows up in logcat, // and will at least give the user a clue about why logcat was // disconnected, if the user happens to be using logcat. debug("ADB timer expired - disabling ADB\n"); navigator.mozSettings.createLock().set( {'debugger.remote-mode': 'disabled'}); } }, updateState: function() { this.umsActive = false; }, updateStateInternal: function() { DEBUG && debug("updateStateInternal: called"); if (this.remoteDebuggerEnabled === undefined || this.lockEnabled === undefined || this.locked === undefined) { // Part of initializing the settings database will cause the observers // to trigger. We want to wait until both have been initialized before // we start changing ther adb state. Without this then we can wind up // toggling adb off and back on again (or on and back off again). // // For completeness, one scenario which toggles adb is using the unagi. // The unagi has adb enabled by default (prior to b2g starting). If you // have the phone lock disabled and remote debugging enabled, then we'll // receive an unlock event and an rde event. However at the time we // receive the unlock event we haven't yet received the rde event, so // we turn adb off momentarily, which disconnects a logcat that might // be running. Changing the defaults (in AdbController) just moves the // problem to a different phone, which has adb disabled by default and // we wind up turning on adb for a short period when we shouldn't. // // By waiting until both values are properly initialized, we avoid // turning adb on or off accidentally. DEBUG && debug("updateState: Waiting for all vars to be initialized"); return; } // Check if we have a remote debugging session going on. If so, we won't // disable adb even if the screen is locked. let isDebugging = USBRemoteDebugger.isDebugging; DEBUG && debug("isDebugging=" + isDebugging); // If USB Mass Storage, USB tethering, or a debug session is active, // then we don't want to disable adb in an automatic fashion (i.e. // when the screen locks or due to timeout). let sysUsbConfig = libcutils.property_get("sys.usb.config").split(","); let usbFuncActive = this.umsActive || isDebugging; usbFuncActive |= (sysUsbConfig.indexOf("rndis") >= 0); usbFuncActive |= (sysUsbConfig.indexOf("mtp") >= 0); let enableAdb = this.remoteDebuggerEnabled && (!(this.lockEnabled && this.locked) || usbFuncActive); let useDisableAdbTimer = true; try { if (Services.prefs.getBoolPref("marionette.defaultPrefs.enabled")) { // Marionette is enabled. Marionette requires that adb be on (and also // requires that remote debugging be off). The fact that marionette // is enabled also implies that we're doing a non-production build, so // we want adb enabled all of the time. enableAdb = true; useDisableAdbTimer = false; } } catch (e) { // This means that the pref doesn't exist. Which is fine. We just leave // enableAdb alone. } // Check wakelock to prevent adb from disconnecting when phone is locked let lockFile = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsIFile); lockFile.initWithPath('/sys/power/wake_lock'); if(lockFile.exists()) { let foStream = Cc["@mozilla.org/network/file-input-stream;1"] .createInstance(Ci.nsIFileInputStream); let coStream = Cc["@mozilla.org/intl/converter-input-stream;1"] .createInstance(Ci.nsIConverterInputStream); let str = {}; foStream.init(lockFile, FileUtils.MODE_RDONLY, 0, 0); coStream.init(foStream, "UTF-8", 0, 0); coStream.readString(-1, str); coStream.close(); foStream.close(); let wakeLockContents = str.value.replace(/\n/, ""); let wakeLockList = wakeLockContents.split(" "); if (wakeLockList.indexOf("adb") >= 0) { enableAdb = true; useDisableAdbTimer = false; DEBUG && debug("Keeping ADB enabled as ADB wakelock is present."); } else { DEBUG && debug("ADB wakelock not found."); } } else { DEBUG && debug("Wake_lock file not found."); } DEBUG && debug("updateState: enableAdb = " + enableAdb + " remoteDebuggerEnabled = " + this.remoteDebuggerEnabled + " lockEnabled = " + this.lockEnabled + " locked = " + this.locked + " usbFuncActive = " + usbFuncActive); // Configure adb. let currentConfig = libcutils.property_get("persist.sys.usb.config"); let configFuncs = currentConfig.split(","); if (currentConfig == "" || currentConfig == "none") { // We want to treat none like the empty string. // "".split(",") yields [""] and not [] configFuncs = []; } let adbIndex = configFuncs.indexOf("adb"); if (enableAdb) { // Add adb to the list of functions, if not already present if (adbIndex < 0) { configFuncs.push("adb"); } } else { // Remove adb from the list of functions, if present if (adbIndex >= 0) { configFuncs.splice(adbIndex, 1); } } let newConfig = configFuncs.join(","); if (newConfig == "") { // Convert the empty string back into none, since that's what init.rc // needs. newConfig = "none"; } if (newConfig != currentConfig) { DEBUG && debug("updateState: currentConfig = " + currentConfig); DEBUG && debug("updateState: newConfig = " + newConfig); try { libcutils.property_set("persist.sys.usb.config", newConfig); } catch(e) { Cu.reportError("Error configuring adb: " + e); } } if (useDisableAdbTimer) { if (enableAdb && !usbFuncActive) { this.startDisableAdbTimer(); } else { this.stopDisableAdbTimer(); } } } }; SettingsListener.observe("lockscreen.locked", false, AdbController.setLockscreenState.bind(AdbController)); SettingsListener.observe("lockscreen.enabled", false, AdbController.setLockscreenEnabled.bind(AdbController));