diff --git a/src/com/android/settings/dashboard/suggestions/SuggestionController.java b/src/com/android/settings/dashboard/suggestions/SuggestionController.java new file mode 100644 index 00000000000..ac20433bc69 --- /dev/null +++ b/src/com/android/settings/dashboard/suggestions/SuggestionController.java @@ -0,0 +1,146 @@ +/* + * 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.settings.dashboard.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 getSuggestions() { + if (!isReady()) { + return null; + } + try { + return mRemoteService.getSuggestions(); + } catch (RemoteException e) { + Log.w(TAG, "Error when calling getSuggestion()", e); + return null; + } + } + + /** + * 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/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java b/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java index f58946dfa1c..6f5c82eddff 100644 --- a/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java +++ b/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixin.java @@ -16,14 +16,8 @@ package com.android.settings.dashboard.suggestions; -import android.annotation.Nullable; 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.VisibleForTesting; import android.util.FeatureFlagUtils; @@ -39,7 +33,8 @@ import java.util.List; /** * Manages IPC communication to SettingsIntelligence for suggestion related services. */ -public class SuggestionControllerMixin implements LifecycleObserver, OnStart, OnStop { +public class SuggestionControllerMixin implements SuggestionController.ServiceConnectionListener, + LifecycleObserver, OnStart, OnStop { @VisibleForTesting static final String FEATURE_FLAG = "new_settings_suggestion"; @@ -47,10 +42,7 @@ public class SuggestionControllerMixin implements LifecycleObserver, OnStart, On private static final boolean DEBUG = false; private final Context mContext; - private final Intent mServiceIntent; - private final ServiceConnection mServiceConnection; - - private ISuggestionService mRemoteService; + private final SuggestionController mSuggestionController; public static boolean isEnabled() { return FeatureFlagUtils.isEnabled(FEATURE_FLAG); @@ -58,11 +50,11 @@ public class SuggestionControllerMixin implements LifecycleObserver, OnStart, On public SuggestionControllerMixin(Context context, Lifecycle lifecycle) { mContext = context.getApplicationContext(); - mServiceIntent = new Intent().setComponent( + mSuggestionController = new SuggestionController(context, new ComponentName( "com.android.settings.intelligence", - "com.android.settings.intelligence.suggestions.SuggestionService")); - mServiceConnection = createServiceConnection(); + "com.android.settings.intelligence.suggestions.SuggestionService"), + this /* serviceConnectionListener */); if (lifecycle != null) { lifecycle.addObserver(this); } @@ -74,73 +66,27 @@ public class SuggestionControllerMixin implements LifecycleObserver, OnStart, On Log.w(TAG, "Feature not enabled, skipping"); return; } - mContext.bindServiceAsUser(mServiceIntent, mServiceConnection, Context.BIND_AUTO_CREATE, - android.os.Process.myUserHandle()); + mSuggestionController.start(); } @Override public void onStop() { - if (mRemoteService != null) { - mRemoteService = null; - mContext.unbindService(mServiceConnection); - } + mSuggestionController.stop(); } - /** - * Get setting suggestions. - */ - @Nullable - public List getSuggestions() { - if (!isReady()) { - return null; - } - try { - return mRemoteService.getSuggestions(); - } catch (RemoteException e) { - Log.w(TAG, "Error when calling getSuggestion()", e); - return null; - } - } - - /** - * Whether or not the manager is ready - */ - private boolean isReady() { - return mRemoteService != null; - } - - @VisibleForTesting - void onServiceConnected() { + @Override + public void onServiceConnected() { // TODO: Call API to get data from a loader instead of in current thread. - final List data = getSuggestions(); - Log.d(TAG, "data size " + (data == null ? 0 : data.size())); + final List data = mSuggestionController.getSuggestions(); + if (DEBUG) { + Log.d(TAG, "data size " + (data == null ? 0 : data.size())); + } } - private void onServiceDisconnected() { - + @Override + public void onServiceDisconnected() { + if (DEBUG) { + Log.d(TAG, "SuggestionService disconnected"); + } } - - /** - * 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); - SuggestionControllerMixin.this.onServiceConnected(); - } - - @Override - public void onServiceDisconnected(ComponentName name) { - mRemoteService = null; - SuggestionControllerMixin.this.onServiceDisconnected(); - } - }; - } - } diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/ShadowSuggestionController.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/ShadowSuggestionController.java new file mode 100644 index 00000000000..b720f83a210 --- /dev/null +++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/ShadowSuggestionController.java @@ -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.settings.dashboard.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 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 suggestions) { + sSuggestions = suggestions; + } + + @Implementation + public List getSuggestions() { + sGetSuggestionCalled = true; + return sSuggestions; + } +} diff --git a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java index ac2026c0cc4..e1184783423 100644 --- a/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/suggestions/SuggestionControllerMixinTest.java @@ -17,11 +17,9 @@ package com.android.settings.dashboard.suggestions; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.service.settings.suggestions.ISuggestionService; import android.util.FeatureFlagUtils; import com.android.settings.TestConfig; @@ -36,32 +34,32 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.annotation.Config; -import org.robolectric.util.ReflectionHelpers; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, shadows = { - SettingsShadowSystemProperties.class + SettingsShadowSystemProperties.class, + ShadowSuggestionController.class }) public class SuggestionControllerMixinTest { @Mock private Context mContext; - @Mock - private ISuggestionService mRemoteService; private Lifecycle mLifecycle; private SuggestionControllerMixin mMixin; - @Before public void setUp() { MockitoAnnotations.initMocks(this); mLifecycle = new Lifecycle(); when(mContext.getApplicationContext()).thenReturn(mContext); + SettingsShadowSystemProperties.set( + FeatureFlagUtils.FFLAG_PREFIX + SuggestionControllerMixin.FEATURE_FLAG, "true"); } @After public void tearDown() { + ShadowSuggestionController.reset(); SettingsShadowSystemProperties.clear(); } @@ -79,13 +77,23 @@ public class SuggestionControllerMixinTest { assertThat(SuggestionControllerMixin.isEnabled()).isFalse(); } + @Test + public void goThroughLifecycle_onStartStop_shouldStartStopService() { + mMixin = new SuggestionControllerMixin(mContext, mLifecycle); + + mLifecycle.onStart(); + assertThat(ShadowSuggestionController.sStartCalled).isTrue(); + + mLifecycle.onStop(); + assertThat(ShadowSuggestionController.sStopCalled).isTrue(); + } + @Test public void onServiceConnected_shouldGetSuggestion() { mMixin = new SuggestionControllerMixin(mContext, mLifecycle); - ReflectionHelpers.setField(mMixin, "mRemoteService", mRemoteService); mMixin.onServiceConnected(); - verify(mRemoteService).getSuggestions(); + assertThat(ShadowSuggestionController.sGetSuggestionCalled).isTrue(); } }