OSDN Git Service

Migrate some Suggestions related classes to SettingsLib(I).
authorZhizhi Liu <zhizhiliu@google.com>
Thu, 18 Jan 2018 19:06:10 +0000 (11:06 -0800)
committerZhizhi Liu <zhizhiliu@google.com>
Tue, 23 Jan 2018 22:07:04 +0000 (14:07 -0800)
Copy classes to SettingsLib from Settings so the code can be
shared with TvSettings.

Test: mmma SettingsLib/tests/robotests

Change-Id: If2d7db9d998af9d262a5e225c3b7a5cacb053542

packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionController.java [new file with mode: 0644]
packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixin.java [new file with mode: 0644]
packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoader.java [new file with mode: 0644]
packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/ShadowSuggestionController.java [new file with mode: 0644]
packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinTest.java [new file with mode: 0644]

diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionController.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionController.java
new file mode 100644 (file)
index 0000000..f740f7c
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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.settingslib.suggestions;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.settings.suggestions.ISuggestionService;
+import android.service.settings.suggestions.Suggestion;
+import android.support.annotation.Nullable;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * A controller class to access suggestion data.
+ */
+public class SuggestionController {
+
+    /**
+     * Callback interface when service is connected/disconnected.
+     */
+    public interface ServiceConnectionListener {
+        /**
+         * Called when service is connected.
+         */
+        void onServiceConnected();
+
+        /**
+         * Called when service is disconnected.
+         */
+        void onServiceDisconnected();
+    }
+
+    private static final String TAG = "SuggestionController";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final Intent mServiceIntent;
+
+    private ServiceConnection mServiceConnection;
+    private ISuggestionService mRemoteService;
+    private ServiceConnectionListener mConnectionListener;
+
+    /**
+     * Create a new controller instance.
+     *
+     * @param context  caller context
+     * @param service  The component name for service.
+     * @param listener listener to receive service connected/disconnected event.
+     */
+    public SuggestionController(Context context, ComponentName service,
+            ServiceConnectionListener listener) {
+        mContext = context.getApplicationContext();
+        mConnectionListener = listener;
+        mServiceIntent = new Intent().setComponent(service);
+        mServiceConnection = createServiceConnection();
+    }
+
+    /**
+     * Start the controller.
+     */
+    public void start() {
+        mContext.bindServiceAsUser(mServiceIntent, mServiceConnection, Context.BIND_AUTO_CREATE,
+                android.os.Process.myUserHandle());
+    }
+
+    /**
+     * Stop the controller.
+     */
+    public void stop() {
+        if (mRemoteService != null) {
+            mRemoteService = null;
+            mContext.unbindService(mServiceConnection);
+        }
+    }
+
+    /**
+     * Get setting suggestions.
+     */
+    @Nullable
+    @WorkerThread
+    public List<Suggestion> getSuggestions() {
+        if (!isReady()) {
+            return null;
+        }
+        try {
+            return mRemoteService.getSuggestions();
+        } catch (NullPointerException e) {
+            Log.w(TAG, "mRemote service detached before able to query", e);
+            return null;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error when calling getSuggestion()", e);
+            return null;
+        }
+    }
+
+    public void dismissSuggestions(Suggestion suggestion) {
+        if (!isReady()) {
+            Log.w(TAG, "SuggestionController not ready, cannot dismiss " + suggestion.getId());
+            return;
+        }
+        try {
+            mRemoteService.dismissSuggestion(suggestion);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error when calling dismissSuggestion()", e);
+        }
+    }
+
+    public void launchSuggestion(Suggestion suggestion) {
+        if (!isReady()) {
+            Log.w(TAG, "SuggestionController not ready, cannot launch " + suggestion.getId());
+            return;
+        }
+
+        try {
+            mRemoteService.launchSuggestion(suggestion);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Error when calling launchSuggestion()", e);
+        }
+    }
+
+    /**
+     * Whether or not the manager is ready
+     */
+    private boolean isReady() {
+        return mRemoteService != null;
+    }
+
+    /**
+     * Create a new {@link ServiceConnection} object to handle service connect/disconnect event.
+     */
+    private ServiceConnection createServiceConnection() {
+        return new ServiceConnection() {
+
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                if (DEBUG) {
+                    Log.d(TAG, "Service is connected");
+                }
+                mRemoteService = ISuggestionService.Stub.asInterface(service);
+                if (mConnectionListener != null) {
+                    mConnectionListener.onServiceConnected();
+                }
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                if (mConnectionListener != null) {
+                    mRemoteService = null;
+                    mConnectionListener.onServiceDisconnected();
+                }
+            }
+        };
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixin.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionControllerMixin.java
new file mode 100644 (file)
index 0000000..46fc32f
--- /dev/null
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 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.settingslib.suggestions;
+
+import android.app.LoaderManager;
+import android.arch.lifecycle.OnLifecycleEvent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Loader;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import java.util.List;
+
+/**
+ * Manages IPC communication to SettingsIntelligence for suggestion related services.
+ */
+public class SuggestionControllerMixin implements SuggestionController.ServiceConnectionListener,
+        android.arch.lifecycle.LifecycleObserver, LoaderManager.LoaderCallbacks<List<Suggestion>> {
+
+    public interface SuggestionControllerHost {
+        /**
+         * Called when suggestion data fetching is ready.
+         */
+        void onSuggestionReady(List<Suggestion> data);
+
+        /**
+         * Returns {@link LoaderManager} associated with the host. If host is not attached to
+         * activity then return null.
+         */
+        @Nullable
+        LoaderManager getLoaderManager();
+    }
+
+    private static final String TAG = "SuggestionCtrlMixin";
+    private static final boolean DEBUG = false;
+
+    private final Context mContext;
+    private final SuggestionController mSuggestionController;
+    private final SuggestionControllerHost mHost;
+
+    private boolean mSuggestionLoaded;
+
+    public SuggestionControllerMixin(Context context, SuggestionControllerHost host,
+            Lifecycle lifecycle, ComponentName componentName) {
+        mContext = context.getApplicationContext();
+        mHost = host;
+        mSuggestionController = new SuggestionController(mContext, componentName,
+                    this /* serviceConnectionListener */);
+        if (lifecycle != null) {
+            lifecycle.addObserver(this);
+        }
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_START)
+    public void onStart() {
+        if (DEBUG) {
+            Log.d(TAG, "SuggestionController started");
+        }
+        mSuggestionController.start();
+    }
+
+    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
+    public void onStop() {
+        if (DEBUG) {
+            Log.d(TAG, "SuggestionController stopped.");
+        }
+        mSuggestionController.stop();
+    }
+
+    @Override
+    public void onServiceConnected() {
+        final LoaderManager loaderManager = mHost.getLoaderManager();
+        if (loaderManager != null) {
+            loaderManager.restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
+                    null /* args */, this /* callback */);
+        }
+    }
+
+    @Override
+    public void onServiceDisconnected() {
+        if (DEBUG) {
+            Log.d(TAG, "SuggestionService disconnected");
+        }
+        final LoaderManager loaderManager = mHost.getLoaderManager();
+        if (loaderManager != null) {
+            loaderManager.destroyLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS);
+        }
+    }
+
+    @Override
+    public Loader<List<Suggestion>> onCreateLoader(int id, Bundle args) {
+        if (id == SuggestionLoader.LOADER_ID_SUGGESTIONS) {
+            mSuggestionLoaded = false;
+            return new SuggestionLoader(mContext, mSuggestionController);
+        }
+        throw new IllegalArgumentException("This loader id is not supported " + id);
+    }
+
+    @Override
+    public void onLoadFinished(Loader<List<Suggestion>> loader, List<Suggestion> data) {
+        mSuggestionLoaded = true;
+        mHost.onSuggestionReady(data);
+    }
+
+    @Override
+    public void onLoaderReset(Loader<List<Suggestion>> loader) {
+        mSuggestionLoaded = false;
+    }
+
+    public boolean isSuggestionLoaded() {
+        return mSuggestionLoaded;
+    }
+
+    public void dismissSuggestion(Suggestion suggestion) {
+        mSuggestionController.dismissSuggestions(suggestion);
+    }
+
+    public void launchSuggestion(Suggestion suggestion) {
+        mSuggestionController.launchSuggestion(suggestion);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoader.java b/packages/SettingsLib/src/com/android/settingslib/suggestions/SuggestionLoader.java
new file mode 100644 (file)
index 0000000..9c1af1e
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.settingslib.suggestions;
+
+import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
+import android.util.Log;
+
+import com.android.settingslib.utils.AsyncLoader;
+
+import java.util.List;
+
+public class SuggestionLoader extends AsyncLoader<List<Suggestion>> {
+
+    public static final int LOADER_ID_SUGGESTIONS = 42;
+    private static final String TAG = "SuggestionLoader";
+
+    private final SuggestionController mSuggestionController;
+
+    public SuggestionLoader(Context context, SuggestionController controller) {
+        super(context);
+        mSuggestionController = controller;
+    }
+
+    @Override
+    protected void onDiscardResult(List<Suggestion> result) {
+
+    }
+
+    @Override
+    public List<Suggestion> loadInBackground() {
+        final List<Suggestion> data = mSuggestionController.getSuggestions();
+        if (data == null) {
+            Log.d(TAG, "data is null");
+        } else {
+            Log.d(TAG, "data size " + data.size());
+        }
+        return data;
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/ShadowSuggestionController.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/ShadowSuggestionController.java
new file mode 100644 (file)
index 0000000..61bc83b
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2017 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.settingslib.suggestions;
+
+import android.service.settings.suggestions.Suggestion;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.List;
+
+@Implements(SuggestionController.class)
+public class ShadowSuggestionController {
+
+    public static boolean sStartCalled;
+    public static boolean sStopCalled;
+    public static boolean sGetSuggestionCalled;
+
+    public static List<Suggestion> sSuggestions;
+
+    public static void reset() {
+        sStartCalled = false;
+        sStopCalled = false;
+        sGetSuggestionCalled = false;
+        sSuggestions = null;
+    }
+
+    @Implementation
+    public void start() {
+        sStartCalled = true;
+    }
+
+    @Implementation
+    public void stop() {
+        sStopCalled = true;
+    }
+
+    public static void setSuggestion(List<Suggestion> suggestions) {
+        sSuggestions = suggestions;
+    }
+
+    @Implementation
+    public List<Suggestion> getSuggestions() {
+        sGetSuggestionCalled = true;
+        return sSuggestions;
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/suggestions/SuggestionControllerMixinTest.java
new file mode 100644 (file)
index 0000000..ed1c405
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 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.settingslib.suggestions;
+
+import static android.arch.lifecycle.Lifecycle.Event.ON_START;
+import static android.arch.lifecycle.Lifecycle.Event.ON_STOP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.LoaderManager;
+import android.arch.lifecycle.LifecycleOwner;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = {
+                ShadowSuggestionController.class
+        })
+public class SuggestionControllerMixinTest {
+
+    @Mock
+    private SuggestionControllerMixin.SuggestionControllerHost mHost;
+
+    private Context mContext;
+    private LifecycleOwner mLifecycleOwner;
+    private Lifecycle mLifecycle;
+    private SuggestionControllerMixin mMixin;
+    private ComponentName mComponentName;
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mLifecycleOwner = () -> mLifecycle;
+        mLifecycle = new Lifecycle(mLifecycleOwner);
+        mComponentName = new ComponentName(
+                "com.android.settings.intelligence",
+                "com.android.settings.intelligence.suggestions.SuggestionService");
+    }
+
+    @After
+    public void tearDown() {
+        ShadowSuggestionController.reset();
+    }
+
+    @Test
+    public void goThroughLifecycle_onStartStop_shouldStartStopController() {
+        mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle, mComponentName);
+
+        mLifecycle.handleLifecycleEvent(ON_START);
+        assertThat(ShadowSuggestionController.sStartCalled).isTrue();
+
+        mLifecycle.handleLifecycleEvent(ON_STOP);
+        assertThat(ShadowSuggestionController.sStopCalled).isTrue();
+    }
+
+    @Test
+    public void onServiceConnected_shouldGetSuggestion() {
+        final LoaderManager loaderManager = mock(LoaderManager.class);
+        when(mHost.getLoaderManager()).thenReturn(loaderManager);
+
+        mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle, mComponentName);
+        mMixin.onServiceConnected();
+
+        verify(loaderManager).restartLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS,
+                null /* args */, mMixin /* callback */);
+    }
+
+    @Test
+    public void onServiceConnected_hostNotAttached_shouldDoNothing() {
+        when(mHost.getLoaderManager()).thenReturn(null);
+
+        mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle, mComponentName);
+        mMixin.onServiceConnected();
+
+        verify(mHost).getLoaderManager();
+    }
+
+    @Test
+    public void onServiceDisconnected_hostNotAttached_shouldDoNothing() {
+        when(mHost.getLoaderManager()).thenReturn(null);
+
+        mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle, mComponentName);
+        mMixin.onServiceDisconnected();
+
+        verify(mHost).getLoaderManager();
+    }
+
+    @Test
+    public void doneLoadingg_shouldSetSuggestionLoaded() {
+        mMixin = new SuggestionControllerMixin(mContext, mHost, mLifecycle, mComponentName);
+
+        mMixin.onLoadFinished(mock(SuggestionLoader.class), null);
+
+        assertThat(mMixin.isSuggestionLoaded()).isTrue();
+
+        mMixin.onLoaderReset(mock(SuggestionLoader.class));
+
+        assertThat(mMixin.isSuggestionLoaded()).isFalse();
+    }
+}