Add basic settings suggestion support

Bug: 74248946
Test: Tested locally

Change-Id: I16daa6171a11a1eb5ff8da0e61c78546984f9f34
diff --git a/Android.mk b/Android.mk
index 587f874..5cbef4d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -53,6 +53,7 @@
   include packages/apps/Car/libs/car-stream-ui-lib/car-stream-ui-lib.mk
   include packages/apps/Car/libs/car-list/car-list.mk
   include packages/apps/Car/libs/car-apps-common/car-apps-common.mk
+  include packages/apps/Car/libs/car-settings-lib/car-settings-lib.mk
   include packages/services/Car/car-support-lib/car-support.mk
   include frameworks/opt/setupwizard/library/common-gingerbread.mk
   include frameworks/base/packages/SettingsLib/common.mk
diff --git a/src/com/android/car/settings/common/Logger.java b/src/com/android/car/settings/common/Logger.java
new file mode 100644
index 0000000..841aa90
--- /dev/null
+++ b/src/com/android/car/settings/common/Logger.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 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.car.settings.common;
+
+import com.android.car.settingslib.log.LoggerBase;
+
+/**
+ * Implementation of {@link LoggerBase} for CarSettings.
+ */
+public final class Logger extends LoggerBase {
+    public Logger(Class<?> cls) {
+        super(cls);
+    }
+
+    @Override
+    protected String getTag() {
+        return "CarSettings";
+    }
+}
diff --git a/src/com/android/car/settings/home/HomepageFragment.java b/src/com/android/car/settings/home/HomepageFragment.java
index bd9244e..e452921 100644
--- a/src/com/android/car/settings/home/HomepageFragment.java
+++ b/src/com/android/car/settings/home/HomepageFragment.java
@@ -25,19 +25,20 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.res.Resources;
 import android.os.Bundle;
-import android.util.DisplayMetrics;
 import android.view.View;
 
 import com.android.car.list.TypedPagedListAdapter;
 import com.android.car.settings.R;
 import com.android.car.settings.applications.ApplicationSettingsFragment;
 import com.android.car.settings.common.ListSettingsFragment;
+import com.android.car.settings.common.Logger;
 import com.android.car.settings.datetime.DatetimeSettingsFragment;
+import com.android.car.settings.suggestions.SettingsSuggestionsController;
 import com.android.car.settings.display.DisplaySettingsFragment;
 import com.android.car.settings.security.ChooseLockTypeFragment;
 import com.android.car.settings.sound.SoundSettingsFragment;
+import com.android.car.settings.suggestions.SuggestionLineItem;
 import com.android.car.settings.system.SystemSettingsFragment;
 import com.android.car.settings.users.UsersListFragment;
 import com.android.car.settings.wifi.CarWifiManager;
@@ -49,11 +50,16 @@
 /**
  * Homepage for settings for car.
  */
-public class HomepageFragment extends ListSettingsFragment implements CarWifiManager.Listener {
-    private static final String TAG = "HomepageFragment";
+public class HomepageFragment extends ListSettingsFragment implements
+        CarWifiManager.Listener,
+        SettingsSuggestionsController.Listener {
+    private static final Logger LOG = new Logger(HomepageFragment.class);
+
+    private SettingsSuggestionsController mSettingsSuggestionsController;
     private CarWifiManager mCarWifiManager;
     private WifiLineItem mWifiLineItem;
     private BluetoothLineItem mBluetoothLineItem;
+    private ArrayList<TypedPagedListAdapter.LineItem> mSettingsLineItems = new ArrayList<>();
 
     private final BroadcastReceiver mBtStateReceiver = new BroadcastReceiver() {
         @Override
@@ -89,6 +95,13 @@
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
+        LOG.v("onActivityCreated: " + savedInstanceState);
+        mSettingsLineItems.clear();
+        mSettingsSuggestionsController =
+                new SettingsSuggestionsController(
+                        getContext(),
+                        getLoaderManager(),
+                        this /* listener */);
         mCarWifiManager = new CarWifiManager(getContext(), this /* listener */);
         mWifiLineItem = new WifiLineItem(getContext(), mCarWifiManager, mFragmentController);
         mBluetoothLineItem = new BluetoothLineItem(getContext(), mFragmentController);
@@ -113,20 +126,15 @@
     public void onStart() {
         super.onStart();
         mCarWifiManager.start();
+        mSettingsSuggestionsController.start();
         getActivity().registerReceiver(mBtStateReceiver, mBtStateChangeFilter);
     }
 
-    private static float convertPixelsToDp(float px, Context context){
-        Resources resources = context.getResources();
-        DisplayMetrics metrics = resources.getDisplayMetrics();
-        float dp = px / ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT);
-        return dp;
-    }
-
     @Override
     public void onStop() {
         super.onStop();
         mCarWifiManager.stop();
+        mSettingsSuggestionsController.stop();
         getActivity().unregisterReceiver(mBtStateReceiver);
     }
 
@@ -138,56 +146,55 @@
 
     @Override
     public ArrayList<TypedPagedListAdapter.LineItem> getLineItems() {
-        ArrayList<TypedPagedListAdapter.LineItem> lineItems = new ArrayList<>();
         ExtraSettingsLoader extraSettingsLoader = new ExtraSettingsLoader(getContext());
         Map<String, Collection<TypedPagedListAdapter.LineItem>> extraSettings =
                 extraSettingsLoader.load();
-        lineItems.add(new SimpleIconTransitionLineItem(
+        mSettingsLineItems.add(new SimpleIconTransitionLineItem(
                 R.string.display_settings,
                 R.drawable.ic_settings_display,
                 getContext(),
                 null,
                 DisplaySettingsFragment.getInstance(),
                 mFragmentController));
-        lineItems.add(new SimpleIconTransitionLineItem(
+        mSettingsLineItems.add(new SimpleIconTransitionLineItem(
                 R.string.sound_settings,
                 R.drawable.ic_settings_sound,
                 getContext(),
                 null,
                 SoundSettingsFragment.getInstance(),
                 mFragmentController));
-        lineItems.add(mWifiLineItem);
-        lineItems.addAll(extraSettings.get(WIRELESS_CATEGORY));
-        lineItems.add(mBluetoothLineItem);
-        lineItems.add(new SimpleIconTransitionLineItem(
+        mSettingsLineItems.add(mWifiLineItem);
+        mSettingsLineItems.addAll(extraSettings.get(WIRELESS_CATEGORY));
+        mSettingsLineItems.add(mBluetoothLineItem);
+        mSettingsLineItems.add(new SimpleIconTransitionLineItem(
                 R.string.applications_settings,
                 R.drawable.ic_settings_applications,
                 getContext(),
                 null,
                 ApplicationSettingsFragment.getInstance(),
                 mFragmentController));
-        lineItems.add(new SimpleIconTransitionLineItem(
+        mSettingsLineItems.add(new SimpleIconTransitionLineItem(
                 R.string.date_and_time_settings_title,
                 R.drawable.ic_settings_date_time,
                 getContext(),
                 null,
                 DatetimeSettingsFragment.getInstance(),
                 mFragmentController));
-        lineItems.add(new SimpleIconTransitionLineItem(
+        mSettingsLineItems.add(new SimpleIconTransitionLineItem(
                 R.string.user_and_account_settings_title,
                 R.drawable.ic_user,
                 getContext(),
                 null,
                 UsersListFragment.newInstance(),
                 mFragmentController));
-        lineItems.add(new SimpleIconTransitionLineItem(
+        mSettingsLineItems.add(new SimpleIconTransitionLineItem(
                 R.string.security_settings_title,
                 R.drawable.ic_lock,
                 getContext(),
                 null,
                 ChooseLockTypeFragment.newInstance(),
                 mFragmentController));
-        lineItems.add(new SimpleIconTransitionLineItem(
+        mSettingsLineItems.add(new SimpleIconTransitionLineItem(
                 R.string.system_setting_title,
                 R.drawable.ic_settings_about,
                 getContext(),
@@ -195,8 +202,15 @@
                 SystemSettingsFragment.getInstance(),
                 mFragmentController));
 
-        lineItems.addAll(extraSettings.get(DEVICE_CATEGORY));
-        lineItems.addAll(extraSettings.get(PERSONAL_CATEGORY));
-        return lineItems;
+        mSettingsLineItems.addAll(extraSettings.get(DEVICE_CATEGORY));
+        mSettingsLineItems.addAll(extraSettings.get(PERSONAL_CATEGORY));
+        return mSettingsLineItems;
+    }
+
+    @Override
+    public void onSuggestionsLoaded(ArrayList<SuggestionLineItem> suggestions) {
+        LOG.v("onDeferredSuggestionsLoaded");
+        mSettingsLineItems.addAll(0, suggestions);
+        mPagedListAdapter.updateList(mSettingsLineItems);
     }
 }
diff --git a/src/com/android/car/settings/suggestions/SettingsSuggestionsController.java b/src/com/android/car/settings/suggestions/SettingsSuggestionsController.java
new file mode 100644
index 0000000..c06f9be
--- /dev/null
+++ b/src/com/android/car/settings/suggestions/SettingsSuggestionsController.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2018 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.car.settings.suggestions;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.service.settings.suggestions.Suggestion;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.suggestions.SuggestionController;
+import com.android.settingslib.suggestions.SuggestionControllerMixin;
+import com.android.settingslib.suggestions.SuggestionLoader;
+import com.android.settingslib.utils.IconCache;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Retrieves suggestions and prepares them for rendering.
+ * Modeled after {@link SuggestionControllerMixin}, differs by implementing support library version
+ * of LoaderManager and Loader. Does not implement use of LifeCycle.
+ */
+public class SettingsSuggestionsController implements
+        SuggestionController.ServiceConnectionListener,
+        LoaderManager.LoaderCallbacks<List<Suggestion>> {
+    private static final Logger LOG = new Logger(SettingsSuggestionsController.class);
+    private static final ComponentName mComponentName = new ComponentName(
+            "com.android.settings.intelligence",
+            "com.android.settings.intelligence.suggestions.SuggestionService");
+
+    private final Context mContext;
+    private final LoaderManager mLoaderManager;
+    private final Listener mListener;
+    private final SuggestionController mSuggestionController;
+    private final IconCache mIconCache;
+
+    public SettingsSuggestionsController(
+            Context context,
+            LoaderManager loaderManager,
+            @NonNull Listener listener) {
+        mContext = context;
+        mLoaderManager = loaderManager;
+        mListener = listener;
+        mIconCache = new IconCache(context);
+        mSuggestionController = new SuggestionController(
+                mContext,
+                mComponentName,
+                this /* serviceConnectionListener */);
+    }
+
+    @Override
+    public void onServiceConnected() {
+        LOG.v("onServiceConnected");
+        mLoaderManager.restartLoader(
+                SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS,
+                null /* args */,
+                this /* callback */);
+    }
+
+    @Override
+    public void onServiceDisconnected() {
+        LOG.v("onServiceDisconnected");
+        cleanupLoader();
+    }
+
+    @NonNull
+    @Override
+    public Loader<List<Suggestion>> onCreateLoader(int id, @Nullable Bundle args) {
+        LOG.v("onCreateLoader: " + id);
+        if (id == SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS) {
+            return new SettingsSuggestionsLoader(mContext, mSuggestionController);
+        }
+        throw new IllegalArgumentException("This loader id is not supported " + id);
+    }
+
+    @Override
+    public void onLoadFinished(
+            @NonNull Loader<List<Suggestion>> loader,
+            List<Suggestion> suggestionList) {
+        LOG.v("onLoadFinished");
+        if (suggestionList == null) {
+            return;
+        }
+        ArrayList<SuggestionLineItem> items = new ArrayList<>();
+        for (final Suggestion suggestion : suggestionList) {
+            LOG.v("Suggestion ID: " + suggestion.getId());
+            Drawable iconDrawable = mIconCache.getIcon(suggestion.getIcon());
+            SuggestionLineItem suggestionLineItem =
+                    new SuggestionLineItem(
+                            suggestion.getTitle(),
+                            suggestion.getSummary(),
+                            iconDrawable,
+                            v -> {
+                                try {
+                                    suggestion.getPendingIntent().send();
+                                    launchSuggestion(suggestion);
+                                } catch (PendingIntent.CanceledException e) {
+                                    LOG.w("Failed to start suggestion " + suggestion.getTitle());
+                                }
+                            });
+            items.add(suggestionLineItem);
+        }
+        mListener.onSuggestionsLoaded(items);
+    }
+
+    @Override
+    public void onLoaderReset(@NonNull Loader<List<Suggestion>> loader) {
+        LOG.v("onLoaderReset");
+    }
+
+    /**
+     * Start the suggestions controller.
+     */
+    public void start() {
+        LOG.v("Start");
+        mSuggestionController.start();
+    }
+
+    /**
+     * Stop the suggestions controller.
+     */
+    public void stop() {
+        LOG.v("Stop");
+        mSuggestionController.stop();
+        cleanupLoader();
+
+    }
+
+    private void cleanupLoader() {
+        LOG.v("cleanupLoader");
+        mLoaderManager.destroyLoader(SuggestionLoader.LOADER_ID_SUGGESTIONS);
+    }
+
+    private void dismissSuggestion(Suggestion suggestion) {
+        LOG.v("dismissSuggestion");
+        mSuggestionController.dismissSuggestions(suggestion);
+    }
+
+    private void launchSuggestion(Suggestion suggestion) {
+        LOG.v("launchSuggestion");
+        mSuggestionController.launchSuggestion(suggestion);
+    }
+
+    /**
+     * Listener interface to notify of data state changes.
+     */
+    public interface Listener {
+        /**
+         * Invoked when deferred setup items have been loaded.
+         * @param suggestions List of deferred setup suggestions.
+         */
+        void onSuggestionsLoaded(@NonNull ArrayList<SuggestionLineItem> suggestions);
+    }
+}
diff --git a/src/com/android/car/settings/suggestions/SettingsSuggestionsLoader.java b/src/com/android/car/settings/suggestions/SettingsSuggestionsLoader.java
new file mode 100644
index 0000000..33c4c0f
--- /dev/null
+++ b/src/com/android/car/settings/suggestions/SettingsSuggestionsLoader.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 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.car.settings.suggestions;
+
+import android.content.Context;
+import android.service.settings.suggestions.Suggestion;
+
+import com.android.car.settingslib.loader.AsyncLoader;
+import com.android.car.settings.common.Logger;
+import com.android.settingslib.suggestions.SuggestionController;
+
+import java.util.List;
+
+/**
+ * Loads suggestions for car settings. Taken from
+ * {@link com.android.settingslib.suggestions.SuggestionLoader}, only change is to extend from car
+ * settings {@link AsyncLoader} which extends from support library {@link AsyncTaskLoader}.
+ */
+public class SettingsSuggestionsLoader extends AsyncLoader<List<Suggestion>> {
+    private static final Logger LOG = new Logger(SettingsSuggestionsLoader.class);
+
+    /**
+     * ID used for loader that loads the settings suggestions. ID value is an arbitrary value.
+     */
+    public static final int LOADER_ID_SUGGESTIONS = 42;
+
+    private final SuggestionController mSuggestionController;
+
+    public SettingsSuggestionsLoader(Context context, SuggestionController controller) {
+        super(context);
+        mSuggestionController = controller;
+    }
+
+    @Override
+    public List<Suggestion> loadInBackground() {
+        final List<Suggestion> data = mSuggestionController.getSuggestions();
+        if (data == null) {
+            LOG.d("data is null");
+        } else {
+            LOG.d("data size " + data.size());
+        }
+        return data;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/car/settings/suggestions/SuggestionLineItem.java b/src/com/android/car/settings/suggestions/SuggestionLineItem.java
new file mode 100644
index 0000000..fee0de7
--- /dev/null
+++ b/src/com/android/car/settings/suggestions/SuggestionLineItem.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 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.car.settings.suggestions;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.car.list.IconTextLineItem;
+
+/**
+ * Represents suggestion list item.
+ */
+public class SuggestionLineItem extends IconTextLineItem {
+
+    private final CharSequence mSummary;
+    private final Drawable mIconDrawable;
+    private final View.OnClickListener mOnClickListener;
+
+    /**
+     * Constructs an IconTextLIneItem with just the title set.
+     */
+    public SuggestionLineItem(
+            CharSequence title,
+            CharSequence summary,
+            Drawable iconDrawable,
+            View.OnClickListener onClickListener) {
+        super(title);
+        mSummary = summary;
+        mIconDrawable = iconDrawable;
+        mOnClickListener = onClickListener;
+    }
+
+    @Override
+    public void setIcon(ImageView iconView) {
+        iconView.setImageDrawable(mIconDrawable);
+    }
+
+    @Override
+    public CharSequence getDesc() {
+        return mSummary;
+    }
+
+    @Override
+    public boolean isExpandable() {
+        return true;
+    }
+
+    @Override
+    public void onClick(View view) {
+        mOnClickListener.onClick(view);
+    }
+}