1 /* 2 * Copyright (C) 2017 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.settings.intelligence.search.sitemap; 18 19 import android.content.Context; 20 import android.database.Cursor; 21 import android.database.sqlite.SQLiteDatabase; 22 import androidx.annotation.WorkerThread; 23 import android.text.TextUtils; 24 import android.util.Log; 25 26 import com.android.settings.intelligence.search.indexing.IndexDatabaseHelper; 27 import com.android.settings.intelligence.search.indexing.IndexDatabaseHelper.SiteMapColumns; 28 29 import java.util.ArrayList; 30 import java.util.List; 31 32 public class SiteMapManager { 33 34 private static final String TAG = "SiteMapManager"; 35 private static final String TOP_LEVEL_SETTINGS 36 = "com.android.settings.homepage.TopLevelSettings"; 37 private static final boolean DEBUG_TIMING = false; 38 39 public static final String[] SITE_MAP_COLUMNS = { 40 SiteMapColumns.PARENT_CLASS, 41 SiteMapColumns.PARENT_TITLE, 42 SiteMapColumns.CHILD_CLASS, 43 SiteMapColumns.CHILD_TITLE, 44 SiteMapColumns.HIGHLIGHTABLE_MENU_KEY 45 }; 46 47 private final List<SiteMapPair> mPairs = new ArrayList<>(); 48 49 private boolean mInitialized; 50 51 /** 52 * Check whether the specified class is top level settings class. 53 */ isTopLevelSettings(String clazz)54 public static boolean isTopLevelSettings(String clazz) { 55 return TextUtils.equals(TOP_LEVEL_SETTINGS, clazz); 56 } 57 58 /** 59 * Given a fragment class name and its screen title, build a breadcrumb from Settings root to 60 * this screen. 61 * <p/> 62 * Not all screens have a full breadcrumb path leading up to root, it's because either some 63 * page in the breadcrumb path is not indexed, or it's only reachable via search. 64 */ 65 @WorkerThread buildBreadCrumb(Context context, String clazz, String screenTitle)66 public synchronized List<String> buildBreadCrumb(Context context, String clazz, 67 String screenTitle) { 68 init(context); 69 final long startTime = System.currentTimeMillis(); 70 final List<String> breadcrumbs = new ArrayList<>(); 71 if (!mInitialized) { 72 Log.w(TAG, "SiteMap is not initialized yet, skipping"); 73 return breadcrumbs; 74 } 75 if (!TextUtils.isEmpty(screenTitle)) { 76 breadcrumbs.add(screenTitle); 77 } 78 String currentClass = clazz; 79 String currentTitle = screenTitle; 80 // Look up current page's parent, if found add it to breadcrumb string list, and repeat. 81 while (true) { 82 final SiteMapPair pair = lookUpParent(currentClass, currentTitle); 83 if (pair == null) { 84 if (DEBUG_TIMING) { 85 Log.d(TAG, "BreadCrumb timing: " + (System.currentTimeMillis() - startTime)); 86 } 87 return breadcrumbs; 88 } 89 final String parentTitle = pair.getParentTitle(); 90 if (!TextUtils.isEmpty(parentTitle)) { 91 breadcrumbs.add(0, parentTitle); 92 } 93 currentClass = pair.getParentClass(); 94 currentTitle = pair.getParentTitle(); 95 } 96 } 97 getTopLevelPair(Context context, String clazz, String screenTitle)98 public synchronized SiteMapPair getTopLevelPair(Context context, String clazz, 99 String screenTitle) { 100 if (!mInitialized) { 101 init(context); 102 } 103 104 // find the default pair 105 SiteMapPair currentPair = null; 106 if (!TextUtils.isEmpty(clazz)) { 107 for (SiteMapPair pair : mPairs) { 108 if (TextUtils.equals(pair.getChildClass(), clazz)) { 109 currentPair = pair; 110 if (TextUtils.isEmpty(screenTitle)) { 111 screenTitle = pair.getChildTitle(); 112 } 113 break; 114 } 115 } 116 } 117 118 // recursively find the top level pair 119 String currentClass = clazz; 120 String currentTitle = screenTitle; 121 while (true) { 122 // look up parent by class and title 123 SiteMapPair pair = lookUpParent(currentClass, currentTitle); 124 if (pair == null) { 125 // fallback option: look up parent only by title 126 pair = lookUpParent(currentTitle); 127 if (pair == null) { 128 return currentPair; 129 } 130 } 131 if (!TextUtils.isEmpty(pair.getHighlightableMenuKey())) { 132 return pair; 133 } 134 currentPair = pair; 135 currentClass = pair.getParentClass(); 136 currentTitle = pair.getParentTitle(); 137 } 138 } 139 140 /** 141 * Initialize a list of {@link SiteMapPair}s. Each pair knows about a single parent-child 142 * page relationship. 143 */ 144 @WorkerThread init(Context context)145 private synchronized void init(Context context) { 146 if (mInitialized) { 147 // Make sure only init once. 148 return; 149 } 150 151 // Will init again if site map table updated, need clear the old data if it's not empty. 152 if (!mPairs.isEmpty()) { 153 mPairs.clear(); 154 } 155 156 final long startTime = System.currentTimeMillis(); 157 // First load site map from static index table. 158 final Context appContext = context.getApplicationContext(); 159 final SQLiteDatabase db = IndexDatabaseHelper.getInstance(appContext).getReadableDatabase(); 160 Cursor sitemap = db.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS, null, 161 null, null, null, null); 162 while (sitemap.moveToNext()) { 163 final SiteMapPair pair = new SiteMapPair( 164 sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_CLASS)), 165 sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_TITLE)), 166 sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_CLASS)), 167 sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_TITLE)), 168 sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.HIGHLIGHTABLE_MENU_KEY)) 169 ); 170 mPairs.add(pair); 171 } 172 sitemap.close(); 173 // Done. 174 mInitialized = true; 175 if (DEBUG_TIMING) { 176 Log.d(TAG, "Init timing: " + (System.currentTimeMillis() - startTime)); 177 } 178 } 179 180 @WorkerThread lookUpParent(String clazz, String title)181 private SiteMapPair lookUpParent(String clazz, String title) { 182 for (SiteMapPair pair : mPairs) { 183 if (TextUtils.equals(pair.getChildClass(), clazz) 184 && TextUtils.equals(title, pair.getChildTitle())) { 185 return pair; 186 } 187 } 188 return null; 189 } 190 191 @WorkerThread lookUpParent(String title)192 private SiteMapPair lookUpParent(String title) { 193 if (TextUtils.isEmpty(title)) { 194 return null; 195 } 196 for (SiteMapPair pair : mPairs) { 197 if (TextUtils.equals(title, pair.getChildTitle())) { 198 return pair; 199 } 200 } 201 return null; 202 } 203 204 @WorkerThread setInitialized(boolean initialized)205 public void setInitialized(boolean initialized) { 206 mInitialized = initialized; 207 } 208 } 209