public interface AndroidDevice {

    String executeCommand(String command);

    List<FileInfo> list(String path);

    void openUrl(String url);

    void pullFile(String remoteFrom, File localTo);

    void pushFile(File localFrom, String remoteTo);
} @Singleton
public final class AndroidDeviceImpl implements AndroidDevice {

    private final IDevice device;

    @Inject
    public AndroidDeviceImpl(final IDevice device) {
        this.device = device;
    }

    @Override
    public String executeCommand(final String cmd) {
        log().debug("Executing command: `{}`.", cmd);
        try (final ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            device.executeShellCommand(cmd, new OutputStreamShellOutputReceiver(bos));
            final String result = new String(bos.toByteArray(), StandardCharsets.UTF_8);
            log().debug("Command `{}` executed with result: `{}`.", cmd, result);
            return result;
        } catch (final Exception ex) {
            log().error("Unable to execute command `{}`.", cmd, ex);
            throw new ExecuteCommandException(cmd);
        }
    }

    @Override
    public List<FileInfo> list(final String path) {
        log().debug("Listing files under path: `{}`.", path);
        try {
            final String s = executeCommand("ls -l " + path);
            final String[] entries = s.split("\r\n");
            final List<FileInfo> fileInfos = new ArrayList<>();
            for (final String entry : entries) {
                String[] data = entry.split(" ");
                if (data.length < 4)
                    continue;
                String attributes = data[0];
                boolean directory = attributes.charAt(0) == 'd';
                String name = data[data.length - 1];

                final FileInfo fi = new FileInfo();
                fi.attribs = attributes;
                fi.directory = directory;
                fi.name = name;
                fi.path = path;
                fi.device = this;

                fileInfos.add(fi);
            }
            log().debug("Found `{}` files under path `{}`.", fileInfos.size(), path);
            return fileInfos;
        } catch (final Exception ex) {
            log().error("Unable to list files under path `{}`.", path, ex);
            throw new AndroidScreenCastRuntimeException(ex);
        }
    }

    @Override
    public void openUrl(final String url) {
        log().debug("Opening URL: `{}`.", url);
        executeCommand("am start " + url);
    }

    @Override
    public void pullFile(final String remote, final File local) {
        log().debug("Pulling remote file `{}` to the local destination: `{}`.", remote, local);
        // ugly hack to call the method without FileEntry
        try {
            if (device.getSyncService() == null)
                throw new AndroidScreenCastRuntimeException("SyncService is null, ADB crashed ?");
            device.getSyncService().pullFile(remote, local.getAbsolutePath(), SyncService.getNullProgressMonitor());
            log().debug("Remote file `{}` pulled to the local destination: `{}`.", remote, local);
        } catch (final Exception ex) {
            log().error("Unable to pull remote file `{}` to the local destination: `{}`.", remote, local, ex);
            throw new AndroidScreenCastRuntimeException(ex);
        }
    }

    @Override
    public void pushFile(final File local, final String remote) {
        log().debug("Pushing local file `{}` to the remote destination: `{}`.", local, remote);
        try {
            if (device.getSyncService() == null)
                throw new AndroidScreenCastRuntimeException("SyncService is null, ADB crashed ?");
            device.getSyncService().pushFile(local.getAbsolutePath(), remote, SyncService.getNullProgressMonitor());
            log().debug("Local file `{}` pushed to the remote destination: `{}`.", local, remote);
        } catch (final Exception ex) {
            log().error("Unable to push local file `{}` to the remote destination: `{}`.", local, remote, ex);
            throw new AndroidScreenCastRuntimeException(ex);
        }
    }

    private enum LogSingleton {
        INSTANCE;

        @SuppressWarnings({"NonSerializableFieldInSerializableClass", "ImmutableEnumChecker"})
        private final Logger value = getLogger(AndroidDeviceImpl.class);
    }

    private static Logger log() {
        return LogSingleton.INSTANCE.value;
    }
} public interface CommandExecutor {

    void execute(Command command);
} @Singleton
public final class ShellCommandExecutor implements CommandExecutor {

    private final IDevice device;
    private final IShellOutputReceiver shellOutputReceiver;
    private final long adbCommandTimeout;

    @Inject
    public ShellCommandExecutor(final IDevice device,
                                final IShellOutputReceiver shellOutputReceiver,
                                @Named(ADB_COMMAND_TIMEOUT_KEY) long adbCommandTimeout) {
        this.device = device;
        this.shellOutputReceiver = shellOutputReceiver;
        this.adbCommandTimeout = adbCommandTimeout;
    }

    @Override
    public void execute(Command command) {
        log().debug("Executing command: {}", command);

        try {
            device.executeShellCommand(command.getFormattedCommand(), shellOutputReceiver,
                    adbCommandTimeout, TimeUnit.SECONDS);
            log().debug("Command {} successfully executed.", command);
        } catch (TimeoutException | AdbCommandRejectedException | ShellCommandUnresponsiveException | IOException e) {
            log().error("An exception happened during command execution: {}.", command, e);
            throw new AdbShellCommandExecutionException(command, e);
        }
    }

    private enum LogSingleton {
        INSTANCE;

        @SuppressWarnings({"NonSerializableFieldInSerializableClass", "ImmutableEnumChecker"})
        private final Logger value = getLogger(ShellCommandExecutor.class);
    }

    private static Logger log() {
        return LogSingleton.INSTANCE.value;
    }
} @Singleton
public final class AdbInputCommandFactory implements InputCommandFactory {

    @Inject
    public AdbInputCommandFactory() {
    }

    @Override
    public KeyCommand getKeyCommand(final int keyCode) {
        final KeyCommand returnKeyCommand = new KeyCommand(keyCode);
        log().debug(String.valueOf(returnKeyCommand));
        return returnKeyCommand;
    }

    @Override
    public KeyCommand getKeyCommand(final InputKeyEvent inputKeyEvent, final boolean longpress) {
        final KeyCommand returnKeyCommand = new KeyCommand(inputKeyEvent, longpress);
        log().debug(String.valueOf(returnKeyCommand));
        return returnKeyCommand;
    }

    @Override
    public SwipeCommand getSwipeCommand(final int x1, final int y1, final int x2, final int y2, final long duration) {
        final SwipeCommand returnSwipeCommand = new SwipeCommand(x1, y1, x2, y2, duration);
        log().debug(String.valueOf(returnSwipeCommand));
        return returnSwipeCommand;
    }

    @Override
    public TapCommand getTapCommand(final int x, final int y) {
        final TapCommand returnTapCommand = new TapCommand(x, y);
        log().debug(String.valueOf(returnTapCommand));
        return returnTapCommand;
    }

    private enum LogSingleton {
        INSTANCE;

        @SuppressWarnings({"NonSerializableFieldInSerializableClass", "ImmutableEnumChecker"})
        private final Logger value = getLogger(AdbInputCommandFactory.class);
    }

    private static Logger log() {
        return LogSingleton.INSTANCE.value;
    }
} public interface InputCommandFactory {

    KeyCommand getKeyCommand(int keyCode);

    KeyCommand getKeyCommand(InputKeyEvent inputKeyEvent, boolean longpress);

    SwipeCommand getSwipeCommand(int x1, int y1, int x2, int y2, long duration);

    TapCommand getTapCommand(int x, int y);
} @Singleton
public final class MultiLineReceiverPrinter extends MultiLineReceiver {

    @Inject
    public MultiLineReceiverPrinter() {
    }

    @Override
    public boolean isCancelled() {
        return false;
    }

    @Override
    public void processNewLines(String[] arg0) {
        for (String elem : arg0) {
            System.out.println(elem);
        }
    }
} @Singleton
public class ApplicationConfiguration {
    private static final String PROPERTIES_LOCATION = "./app.properties";
    private final Properties properties;

    @Inject
    public ApplicationConfiguration() {
        properties = initProperties();
    }

    private Properties initProperties() {
        final Properties properties = new Properties();
        loadProperties(properties, PROPERTIES_LOCATION);
        return properties;
    }

    private void loadProperties(final Properties properties, final String propertiesLocation) {
        final File propertiesFile = new File(propertiesLocation);
        if (propertiesFile.exists()) {
            try (final FileInputStream fis = new FileInputStream(propertiesFile)) {
                properties.load(fis);
            } catch (final IOException e) {
                throw new AndroidScreenCastRuntimeException(e);
            }
        }
    }

    public String getProperty(final ApplicationConfigurationProperty configurationProperty) {
        final String property = properties.getProperty(configurationProperty.getPropertyKey(), configurationProperty.getDefaultValue());
        return property;
    }
} public enum ApplicationConfigurationProperty {
    ADB_PATH(ADB_PATH_KEY, ""),
    ADB_DEVICE_TIMEOUT(ADB_DEVICE_TIMEOUT_KEY, "30"),
    ADB_COMMAND_TIMEOUT(ADB_COMMAND_TIMEOUT_KEY, "5"),
    APP_WINDOW_WIDTH(APP_WINDOW_WIDTH_KEY, "1024"),
    APP_WINDOW_HEIGHT(APP_WINDOW_HEIGHT_KEY, "768"),
    APP_NATIVE_LOOK(APP_NATIVE_LOOK_KEY, "true"),
    APP_DEBUG_ENABLED(APP_DEBUG_ENABLED_KEY, "false");
    private final String propertyKey;
    private final String defaultValue;

    ApplicationConfigurationProperty(final String propertyKey, String defaultValue) {
        this.propertyKey = propertyKey;
        this.defaultValue = defaultValue;
    }

    public String getPropertyKey() {
        return propertyKey;
    }

    public String getDefaultValue() {
        return defaultValue;
    }
} public final class ApplicationConfigurationPropertyKeys {
    public static final String ADB_PATH_KEY = "adb.path";
    public static final String ADB_DEVICE_TIMEOUT_KEY = "adb.device.timeout";
    public static final String ADB_COMMAND_TIMEOUT_KEY = "adb.command.timeout";
    public static final String APP_WINDOW_WIDTH_KEY = "app.window.width";
    public static final String APP_WINDOW_HEIGHT_KEY = "app.window.height";
    public static final String APP_NATIVE_LOOK_KEY = "app.native.look";
    public static final String APP_DEBUG_ENABLED_KEY = "app.debug.enabled";

    private ApplicationConfigurationPropertyKeys() {
        //
    }
}
import javax.inject.Named;
import javax.inject.Singleton;
import static com.github.xsavikx.androidscreencast.configuration.ApplicationConfigurationProperty.*;
import static com.github.xsavikx.androidscreencast.configuration.ApplicationConfigurationPropertyKeys.*;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.NullOutputReceiver;
import com.github.xsavikx.androidscreencast.api.AndroidDevice;
import com.github.xsavikx.androidscreencast.api.AndroidDeviceImpl;
import com.github.xsavikx.androidscreencast.api.command.executor.CommandExecutor;
import com.github.xsavikx.androidscreencast.api.command.executor.ShellCommandExecutor;
import com.github.xsavikx.androidscreencast.api.command.factory.AdbInputCommandFactory;
import com.github.xsavikx.androidscreencast.api.command.factory.InputCommandFactory;
import com.github.xsavikx.androidscreencast.api.injector.MultiLineReceiverPrinter;
import com.github.xsavikx.androidscreencast.configuration.ApplicationConfiguration;
import dagger.Module;
import dagger.Provides;
/*
 * Copyright 2020 Yurii Serhiichuk
 *
 * 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.github.xsavikx.androidscreencast.dagger;




@Module
public class ApiModule {
    @Singleton
    @Named(ADB_COMMAND_TIMEOUT_KEY)
    @Provides
    public static long adbCommandTimeout(ApplicationConfiguration applicationConfiguration) {
        return Long.valueOf(applicationConfiguration.getProperty(ADB_COMMAND_TIMEOUT));
    }

    @Singleton
    @Named(ADB_DEVICE_TIMEOUT_KEY)
    @Provides
    public static long adbDeviceTimeout(ApplicationConfiguration applicationConfiguration) {
        return Long.valueOf(applicationConfiguration.getProperty(ADB_DEVICE_TIMEOUT));
    }

    @Singleton
    @Named(ADB_PATH_KEY)
    @Provides
    public static String adbPath(ApplicationConfiguration applicationConfiguration) {
        return applicationConfiguration.getProperty(ADB_PATH);
    }

    @Singleton
    @Provides
    public static CommandExecutor commandExecutor(ShellCommandExecutor shellCommandExecutor) {
        return shellCommandExecutor;
    }

    @Singleton
    @Provides
    public static AndroidDevice androidDevice(AndroidDeviceImpl androidDevice) {
        return androidDevice;
    }

    @Singleton
    @Provides
    public static InputCommandFactory inputCommandFactory(AdbInputCommandFactory adbInputCommandFactory) {
        return adbInputCommandFactory;
    }

    @Singleton
    @Named(APP_DEBUG_ENABLED_KEY)
    @Provides
    public static boolean isDebugEnabled(ApplicationConfiguration applicationConfiguration) {
        return Boolean.valueOf(applicationConfiguration.getProperty(APP_DEBUG_ENABLED));
    }

    @Singleton
    @Provides
    public static IShellOutputReceiver iShellOutputReceiver(@Named(APP_DEBUG_ENABLED_KEY) boolean isDebugEnabled, MultiLineReceiverPrinter multiLineReceiverPrinter) {