<commit_before>/*
 *  Copyright 2016 Google Inc. All Rights Reserved.
 *
 *  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.google.android.apps.forscience.whistlepunk.devicemanager;

import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.ArrayMap;

import com.google.android.apps.forscience.javalib.Consumer;
import com.google.android.apps.forscience.javalib.Delay;
import com.google.android.apps.forscience.javalib.MaybeConsumer;
import com.google.android.apps.forscience.javalib.MaybeConsumers;
import com.google.android.apps.forscience.javalib.Scheduler;
import com.google.android.apps.forscience.javalib.Success;
import com.google.android.apps.forscience.whistlepunk.Clock;
import com.google.android.apps.forscience.whistlepunk.DataController;
import com.google.android.apps.forscience.whistlepunk.ExternalSensorProvider;
import com.google.android.apps.forscience.whistlepunk.LoggingConsumer;
import com.google.android.apps.forscience.whistlepunk.SensorAppearanceProvider;
import com.google.android.apps.forscience.whistlepunk.SensorRegistry;
import com.google.android.apps.forscience.whistlepunk.api.scalarinput.InputDeviceSpec;
import com.google.android.apps.forscience.whistlepunk.api.scalarinput.TaskPool;
import com.google.android.apps.forscience.whistlepunk.metadata.ExperimentSensors;
import com.google.android.apps.forscience.whistlepunk.metadata.ExternalSensorSpec;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Runnables;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Remembers sensors that have been found during scanning, and can expose them by adding them
 * to a PreferenceCategory.
 */
public class ConnectableSensorRegistry {
    public static final String TAG = "ConSensorRegistry";

    // Don't remove a sensor unless it's been gone 15 seconds
    private static final long ASSUME_GONE_TIMEOUT_MILLIS = 15_000;
    private static final String EXTERNAL_SENSOR_KEY_PREFIX = "sensorKey";

    private final DataController mDataController;
    private final Map<String, ExternalSensorDiscoverer> mDiscoverers;

    private final DevicesPresenter mPresenter;
    private final Map<String, ConnectableSensor> mSensors = new ArrayMap<>();
    private final Map<String, ExternalSensorDiscoverer.SettingsInterface>
            mSettingsIntents = new ArrayMap<>();
    private final Scheduler mScheduler;
    private boolean mScanning = false;
    private int mScanCount = 0;

    // Maps from sensorKey to the timestamp (from mClock) of the last time it showed up in a scan.
    private Map<String, Long> mMostRecentlySeen = new ArrayMap<>();

    private int mKeyNum = 0;
    private String mExperimentId = null;
    private Clock mClock;
    private DeviceOptionsDialog.DeviceOptionsListener mOptionsListener;
    private DeviceRegistry mDeviceRegistry;
    private final SensorAppearanceProvider mAppearanceProvider;
    private Runnable mTimeoutRunnable;

    // TODO: reduce parameter list?
    public ConnectableSensorRegistry(DataController dataController,
            Map<String, ExternalSensorDiscoverer> discoverers, DevicesPresenter presenter,
            Scheduler scheduler, Clock clock,
            DeviceOptionsDialog.DeviceOptionsListener optionsListener,
            DeviceRegistry deviceRegistry, SensorAppearanceProvider appearanceProvider) {
        mDataController = dataController;
        mDiscoverers = discoverers;
        mPresenter = presenter;
        mScheduler = scheduler;
        mClock = clock;
        mOptionsListener = optionsListener;
        mDeviceRegistry = deviceRegistry;
        mAppearanceProvider = appearanceProvider;
    }

    public void pair(final String sensorKey) {
        final ExternalSensorDiscoverer.SettingsInterface settings = mSettingsIntents.get(sensorKey);
        addSensorIfNecessary(sensorKey, getPairedGroup().getSensorCount(),
                new LoggingConsumer<ConnectableSensor>(TAG, "Add external sensor") {
                    @Override
                    public void success(final ConnectableSensor sensor) {
                        getPairedGroup().replaceSensor(sensorKey, sensor);
                        mAppearanceProvider.loadAppearances(
                                new LoggingConsumer<Success>(TAG, "Load appearance") {
                                    @Override
                                    public void success(Success value) {
                                        if (sensor.shouldShowOptionsOnConnect()
                                                && settings != null) {
                                            mPresenter.showSensorOptions(mExperimentId,
                                                    sensor.getConnectedSensorId(), settings);
                                        }
                                    }
                                });
                    }
                });
    }

    public void refresh(final boolean clearSensorCache, final SensorRegistry sr) {
        Preconditions.checkNotNull(sr);
        stopScanningInDiscoverers();
        mDataController.getMyDevices(
                new LoggingConsumer<List<InputDeviceSpec>>(TAG, "Load my devices") {
                    @Override
                    public void success(List<InputDeviceSpec> myDevices) {
                        onMyDevicesLoaded(myDevices, clearSensorCache, sr);
                    }
                });
    }

    private void onMyDevicesLoaded(List<InputDeviceSpec> myDevices, final boolean clearSensorCache,
            final SensorRegistry sr) {
        Preconditions.checkNotNull(sr);
        for (final InputDeviceSpec device : myDevices) {<commit_msg>Fix NPE when loads complete after fragment destruction.<commit_after>/*
 *  Copyright 2016 Google Inc. All Rights Reserved.
 *
 *  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.google.android.apps.forscience.whistlepunk.devicemanager;

import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.ArrayMap;

import com.google.android.apps.forscience.javalib.Consumer;
import com.google.android.apps.forscience.javalib.Delay;
import com.google.android.apps.forscience.javalib.MaybeConsumer;
import com.google.android.apps.forscience.javalib.MaybeConsumers;
import com.google.android.apps.forscience.javalib.Scheduler;
import com.google.android.apps.forscience.javalib.Success;
import com.google.android.apps.forscience.whistlepunk.Clock;
import com.google.android.apps.forscience.whistlepunk.DataController;
import com.google.android.apps.forscience.whistlepunk.ExternalSensorProvider;
import com.google.android.apps.forscience.whistlepunk.LoggingConsumer;
import com.google.android.apps.forscience.whistlepunk.SensorAppearanceProvider;
import com.google.android.apps.forscience.whistlepunk.SensorRegistry;
import com.google.android.apps.forscience.whistlepunk.api.scalarinput.InputDeviceSpec;
import com.google.android.apps.forscience.whistlepunk.api.scalarinput.TaskPool;
import com.google.android.apps.forscience.whistlepunk.metadata.ExperimentSensors;
import com.google.android.apps.forscience.whistlepunk.metadata.ExternalSensorSpec;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Runnables;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Remembers sensors that have been found during scanning, and can expose them by adding them
 * to a PreferenceCategory.
 */
public class ConnectableSensorRegistry {
    public static final String TAG = "ConSensorRegistry";

    // Don't remove a sensor unless it's been gone 15 seconds
    private static final long ASSUME_GONE_TIMEOUT_MILLIS = 15_000;
    private static final String EXTERNAL_SENSOR_KEY_PREFIX = "sensorKey";

    private final DataController mDataController;
    private final Map<String, ExternalSensorDiscoverer> mDiscoverers;

    private final DevicesPresenter mPresenter;
    private final Map<String, ConnectableSensor> mSensors = new ArrayMap<>();
    private final Map<String, ExternalSensorDiscoverer.SettingsInterface>
            mSettingsIntents = new ArrayMap<>();
    private final Scheduler mScheduler;
    private boolean mScanning = false;
    private int mScanCount = 0;

    // Maps from sensorKey to the timestamp (from mClock) of the last time it showed up in a scan.
    private Map<String, Long> mMostRecentlySeen = new ArrayMap<>();

    private int mKeyNum = 0;
    private String mExperimentId = null;
    private Clock mClock;
    private DeviceOptionsDialog.DeviceOptionsListener mOptionsListener;
    private DeviceRegistry mDeviceRegistry;
    private final SensorAppearanceProvider mAppearanceProvider;
    private Runnable mTimeoutRunnable;

    // TODO: reduce parameter list?
    public ConnectableSensorRegistry(DataController dataController,
            Map<String, ExternalSensorDiscoverer> discoverers, DevicesPresenter presenter,
            Scheduler scheduler, Clock clock,
            DeviceOptionsDialog.DeviceOptionsListener optionsListener,
            DeviceRegistry deviceRegistry, SensorAppearanceProvider appearanceProvider) {
        mDataController = dataController;
        mDiscoverers = discoverers;
        mPresenter = presenter;
        mScheduler = scheduler;
        mClock = clock;
        mOptionsListener = optionsListener;
        mDeviceRegistry = deviceRegistry;
        mAppearanceProvider = appearanceProvider;
    }

    public void pair(final String sensorKey) {
        final ExternalSensorDiscoverer.SettingsInterface settings = mSettingsIntents.get(sensorKey);
        addSensorIfNecessary(sensorKey, getPairedGroup().getSensorCount(),
                new LoggingConsumer<ConnectableSensor>(TAG, "Add external sensor") {
                    @Override
                    public void success(final ConnectableSensor sensor) {
                        if (mPresenter.isDestroyed()) {
                            return;
                        }
                        getPairedGroup().replaceSensor(sensorKey, sensor);
                        mAppearanceProvider.loadAppearances(
                                new LoggingConsumer<Success>(TAG, "Load appearance") {
                                    @Override
                                    public void success(Success value) {
                                        if (sensor.shouldShowOptionsOnConnect()
                                                && settings != null) {
                                            mPresenter.showSensorOptions(mExperimentId,
                                                    sensor.getConnectedSensorId(), settings);
                                        }
                                    }
                                });
                    }
                });
    }

    public void refresh(final boolean clearSensorCache, final SensorRegistry sr) {
        Preconditions.checkNotNull(sr);
        stopScanningInDiscoverers();
        mDataController.getMyDevices(
                new LoggingConsumer<List<InputDeviceSpec>>(TAG, "Load my devices") {
                    @Override
                    public void success(List<InputDeviceSpec> myDevices) {
                        if (mPresenter.isDestroyed()) {
                            return;
                        }
                        onMyDevicesLoaded(myDevices, clearSensorCache, sr);
                    }
                });
    }

    private void onMyDevicesLoaded(List<InputDeviceSpec> myDevices, final boolean clearSensorCache,
            final SensorRegistry sr) {
        Preconditions.checkNotNull(sr);
        for (final InputDeviceSpec device : myDevices) {
{'fraction_non_alphanumeric': 0.07219579561147767, 'fraction_numerical': 0.002608562221881234, 'mean_word_length': 2.7554019014693174, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5050026', 'n_tokens_mistral': 3375, 'n_tokens_neox': 3240, 'n_words': 811}