1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.settings.suggestions; 18 19 import android.app.PendingIntent; 20 import android.car.drivingstate.CarUxRestrictions; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.os.Bundle; 24 import android.service.settings.suggestions.Suggestion; 25 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 import androidx.loader.app.LoaderManager; 29 import androidx.loader.content.Loader; 30 import androidx.preference.Preference; 31 import androidx.preference.PreferenceGroup; 32 33 import com.android.car.settings.common.FragmentController; 34 import com.android.car.settings.common.Logger; 35 import com.android.car.settings.common.PreferenceController; 36 import com.android.settingslib.suggestions.SuggestionController; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /** 42 * Injects {@link SuggestionPreference} instances loaded from the SuggestionService at the 43 * location in the hierarchy of the controller's placeholder preference. The placeholder should 44 * be a {@link PreferenceGroup} which sets the controller attribute to the fully qualified name 45 * of this class. 46 * 47 * <p>For example: 48 * <pre>{@code 49 * <PreferenceCategory 50 * android:key="@string/pk_suggestions" 51 * android:title="@string/suggestions_title" 52 * settings:controller="com.android.settings.suggestions.SuggestionsPreferenceController"/> 53 * }</pre> 54 */ 55 public class SuggestionsPreferenceController extends 56 PreferenceController<PreferenceGroup> implements 57 SuggestionController.ServiceConnectionListener, 58 LoaderManager.LoaderCallbacks<List<Suggestion>>, SuggestionPreference.Callback { 59 60 private static final Logger LOG = new Logger(SuggestionsPreferenceController.class); 61 62 // These values are hard coded until we receive the OK to plumb them through 63 // SettingsIntelligence. This is ok as right now only SUW uses this framework. 64 private static final ComponentName COMPONENT_NAME = new ComponentName( 65 "com.android.settings.intelligence", 66 "com.android.settings.intelligence.suggestions.SuggestionService"); 67 68 private final SuggestionController mSuggestionController; 69 private List<Suggestion> mSuggestionsList = new ArrayList<>(); 70 private LoaderManager mLoaderManager; 71 SuggestionsPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)72 public SuggestionsPreferenceController(Context context, String preferenceKey, 73 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 74 super(context, preferenceKey, fragmentController, uxRestrictions); 75 mSuggestionController = new SuggestionController(context, 76 COMPONENT_NAME, /* serviceConnectionListener= */ this); 77 } 78 79 @Override getPreferenceType()80 protected Class<PreferenceGroup> getPreferenceType() { 81 return PreferenceGroup.class; 82 } 83 84 /** 85 * Sets the {@link LoaderManager} used to load suggestions. 86 */ setLoaderManager(LoaderManager loaderManager)87 public void setLoaderManager(LoaderManager loaderManager) { 88 mLoaderManager = loaderManager; 89 } 90 91 /** 92 * Verifies that the controller was properly initialized with 93 * {@link #setLoaderManager(LoaderManager)}. 94 * 95 * @throws IllegalStateException if the loader manager is {@code null} 96 */ 97 @Override checkInitialized()98 protected void checkInitialized() { 99 LOG.v("checkInitialized"); 100 if (mLoaderManager == null) { 101 throw new IllegalStateException( 102 "SuggestionPreferenceController must be initialized by calling " 103 + "setLoaderManager(LoaderManager)"); 104 } 105 } 106 107 /** Starts the suggestions controller. */ 108 @Override onStartInternal()109 protected void onStartInternal() { 110 LOG.v("onStartInternal"); 111 mSuggestionController.start(); 112 } 113 114 /** Stops the suggestions controller. */ 115 @Override onStopInternal()116 protected void onStopInternal() { 117 LOG.v("onStopInternal"); 118 mSuggestionController.stop(); 119 cleanupLoader(); 120 } 121 122 @Override onServiceConnected()123 public void onServiceConnected() { 124 LOG.v("onServiceConnected"); 125 mLoaderManager.restartLoader(SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS, /* args= */ 126 null, /* callback= */ this); 127 } 128 129 @Override onServiceDisconnected()130 public void onServiceDisconnected() { 131 LOG.v("onServiceDisconnected"); 132 cleanupLoader(); 133 } 134 135 @NonNull 136 @Override onCreateLoader(int id, @Nullable Bundle args)137 public Loader<List<Suggestion>> onCreateLoader(int id, @Nullable Bundle args) { 138 LOG.v("onCreateLoader: " + id); 139 if (id == SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS) { 140 return new SettingsSuggestionsLoader(getContext(), mSuggestionController); 141 } 142 throw new IllegalArgumentException("This loader id is not supported " + id); 143 } 144 145 146 @Override onLoadFinished(@onNull Loader<List<Suggestion>> loader, List<Suggestion> suggestions)147 public void onLoadFinished(@NonNull Loader<List<Suggestion>> loader, 148 List<Suggestion> suggestions) { 149 LOG.v("onLoadFinished"); 150 if (suggestions == null) { 151 // Load started before the service was ready. 152 return; 153 } 154 155 updateSuggestionPreferences(suggestions); 156 mSuggestionsList = new ArrayList<>(suggestions); 157 } 158 updateSuggestionPreferences(List<Suggestion> suggestions)159 private void updateSuggestionPreferences(List<Suggestion> suggestions) { 160 // Remove suggestions that are not in the new list. 161 for (Suggestion oldSuggestion : mSuggestionsList) { 162 boolean isInNewSuggestionList = false; 163 for (Suggestion suggestion : suggestions) { 164 if (oldSuggestion.getId().equals(suggestion.getId())) { 165 isInNewSuggestionList = true; 166 break; 167 } 168 } 169 if (!isInNewSuggestionList) { 170 getPreference().removePreference( 171 getPreference().findPreference(getSuggestionPreferenceKey(oldSuggestion))); 172 } 173 } 174 175 // Add suggestions that are not in the old list and update the existing suggestions. 176 for (Suggestion suggestion : suggestions) { 177 Preference curPref = getPreference().findPreference( 178 getSuggestionPreferenceKey(suggestion)); 179 if (curPref == null) { 180 SuggestionPreference newSuggPref = new SuggestionPreference(getContext(), 181 suggestion, /* callback= */ this); 182 getPreference().addPreference(newSuggPref); 183 } else { 184 ((SuggestionPreference) curPref).updateSuggestion(suggestion); 185 } 186 } 187 188 refreshUi(); 189 } 190 191 @Override onLoaderReset(@onNull Loader<List<Suggestion>> loader)192 public void onLoaderReset(@NonNull Loader<List<Suggestion>> loader) { 193 LOG.v("onLoaderReset"); 194 } 195 196 @Override launchSuggestion(SuggestionPreference preference)197 public void launchSuggestion(SuggestionPreference preference) { 198 LOG.v("launchSuggestion"); 199 Suggestion suggestion = preference.getSuggestion(); 200 try { 201 if (suggestion.getPendingIntent() != null) { 202 suggestion.getPendingIntent().send(); 203 mSuggestionController.launchSuggestion(suggestion); 204 } else { 205 LOG.w("Suggestion with null pending intent " + suggestion.getId()); 206 } 207 } catch (PendingIntent.CanceledException e) { 208 LOG.w("Failed to start suggestion " + suggestion.getId()); 209 } 210 } 211 212 @Override dismissSuggestion(SuggestionPreference preference)213 public void dismissSuggestion(SuggestionPreference preference) { 214 LOG.v("dismissSuggestion"); 215 Suggestion suggestion = preference.getSuggestion(); 216 mSuggestionController.dismissSuggestions(suggestion); 217 mSuggestionsList.remove(suggestion); 218 getPreference().removePreference(preference); 219 refreshUi(); 220 } 221 222 @Override updateState(PreferenceGroup preference)223 protected void updateState(PreferenceGroup preference) { 224 preference.setVisible(preference.getPreferenceCount() > 0); 225 } 226 cleanupLoader()227 private void cleanupLoader() { 228 LOG.v("cleanupLoader"); 229 mLoaderManager.destroyLoader(SettingsSuggestionsLoader.LOADER_ID_SUGGESTIONS); 230 } 231 getSuggestionPreferenceKey(Suggestion suggestion)232 private String getSuggestionPreferenceKey(Suggestion suggestion) { 233 return SuggestionPreference.SUGGESTION_PREFERENCE_KEY + suggestion.getId(); 234 } 235 } 236