
com.android.ddmlib.PropertyFetcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ddmlib Show documentation
Show all versions of ddmlib Show documentation
Library providing APIs to talk to Android devices
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ddmlib;
import com.android.annotations.NonNull;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.SettableFuture;
import java.util.Map;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Fetches and caches 'getprop' values from device.
*/
class PropertyFetcher {
/** the amount of time to wait between unsuccessful prop fetch attempts */
private static final String GETPROP_COMMAND = "getprop"; //$NON-NLS-1$
private static final Pattern GETPROP_PATTERN = Pattern.compile("^\\[([^]]+)\\]\\:\\s*\\[(.*)\\]$"); //$NON-NLS-1$
private static final int GETPROP_TIMEOUT_SEC = 2;
private static final int EXPECTED_PROP_COUNT = 150;
private enum CacheState {
UNPOPULATED, FETCHING, POPULATED
}
/**
* Shell output parser for a getprop command
*/
@VisibleForTesting
static class GetPropReceiver extends MultiLineReceiver {
private final Map mCollectedProperties =
Maps.newHashMapWithExpectedSize(EXPECTED_PROP_COUNT);
@Override
public void processNewLines(String[] lines) {
// We receive an array of lines. We're expecting
// to have the build info in the first line, and the build
// date in the 2nd line. There seems to be an empty line
// after all that.
for (String line : lines) {
if (line.isEmpty() || line.startsWith("#")) {
continue;
}
Matcher m = GETPROP_PATTERN.matcher(line);
if (m.matches()) {
String label = m.group(1);
String value = m.group(2);
if (!label.isEmpty()) {
mCollectedProperties.put(label, value);
}
}
}
}
@Override
public boolean isCancelled() {
return false;
}
Map getCollectedProperties() {
return mCollectedProperties;
}
}
private final Map mProperties = Maps.newHashMapWithExpectedSize(
EXPECTED_PROP_COUNT);
private final IDevice mDevice;
private CacheState mCacheState = CacheState.UNPOPULATED;
private final Map> mPendingRequests =
Maps.newHashMapWithExpectedSize(4);
public PropertyFetcher(IDevice device) {
mDevice = device;
}
/**
* Returns the full list of cached properties.
*/
public synchronized Map getProperties() {
return mProperties;
}
/**
* Make a possibly asynchronous request for a system property value.
*
* @param name the property name to retrieve
* @return a {@link Future} that can be used to retrieve the prop value
*/
@NonNull
public synchronized Future getProperty(@NonNull String name) {
SettableFuture result;
if (mCacheState.equals(CacheState.FETCHING)) {
result = addPendingRequest(name);
} else if (mDevice.isOnline() && mCacheState.equals(CacheState.UNPOPULATED) || !isRoProp(name)) {
// cache is empty, or this is a volatile prop that requires a query
result = addPendingRequest(name);
mCacheState = CacheState.FETCHING;
initiatePropertiesQuery();
} else {
result = SettableFuture.create();
// cache is populated and this is a ro prop
result.set(mProperties.get(name));
}
return result;
}
private SettableFuture addPendingRequest(String name) {
SettableFuture future = mPendingRequests.get(name);
if (future == null) {
future = SettableFuture.create();
mPendingRequests.put(name, future);
}
return future;
}
private void initiatePropertiesQuery() {
String threadName = String.format("query-prop-%s", mDevice.getSerialNumber());
Thread propThread = new Thread(threadName) {
@Override
public void run() {
try {
GetPropReceiver propReceiver = new GetPropReceiver();
mDevice.executeShellCommand(GETPROP_COMMAND, propReceiver, GETPROP_TIMEOUT_SEC,
TimeUnit.SECONDS);
populateCache(propReceiver.getCollectedProperties());
} catch (Exception e) {
handleException(e);
}
}
};
propThread.setDaemon(true);
propThread.start();
}
private synchronized void populateCache(@NonNull Map props) {
mCacheState = props.isEmpty() ? CacheState.UNPOPULATED : CacheState.POPULATED;
if (!props.isEmpty()) {
mProperties.putAll(props);
}
for (Map.Entry> entry : mPendingRequests.entrySet()) {
entry.getValue().set(mProperties.get(entry.getKey()));
}
mPendingRequests.clear();
}
private synchronized void handleException(Exception e) {
mCacheState = CacheState.UNPOPULATED;
Log.w("PropertyFetcher",
String.format("%s getting properties for device %s: %s",
e.getClass().getSimpleName(), mDevice.getSerialNumber(),
e.getMessage()));
for (Map.Entry> entry : mPendingRequests.entrySet()) {
entry.getValue().setException(e);
}
mPendingRequests.clear();
}
/**
* Return true if cache is populated.
*
* @deprecated implementation detail
*/
@Deprecated
public synchronized boolean arePropertiesSet() {
return CacheState.POPULATED.equals(mCacheState);
}
private static boolean isRoProp(@NonNull String propName) {
return propName.startsWith("ro.") || propName.equals(IDevice.PROP_DEVICE_EMULATOR_DENSITY);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy