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 boolean DEBUG_TIMING = false;
36 
37     public static final String[] SITE_MAP_COLUMNS = {
38             SiteMapColumns.PARENT_CLASS,
39             SiteMapColumns.PARENT_TITLE,
40             SiteMapColumns.CHILD_CLASS,
41             SiteMapColumns.CHILD_TITLE
42     };
43 
44     private final List<SiteMapPair> mPairs = new ArrayList<>();
45 
46     private boolean mInitialized;
47 
48     /**
49      * Given a fragment class name and its screen title, build a breadcrumb from Settings root to
50      * this screen.
51      * <p/>
52      * Not all screens have a full breadcrumb path leading up to root, it's because either some
53      * page in the breadcrumb path is not indexed, or it's only reachable via search.
54      */
55     @WorkerThread
buildBreadCrumb(Context context, String clazz, String screenTitle)56     public synchronized List<String> buildBreadCrumb(Context context, String clazz,
57             String screenTitle) {
58         init(context);
59         final long startTime = System.currentTimeMillis();
60         final List<String> breadcrumbs = new ArrayList<>();
61         if (!mInitialized) {
62             Log.w(TAG, "SiteMap is not initialized yet, skipping");
63             return breadcrumbs;
64         }
65         if (!TextUtils.isEmpty(screenTitle)) {
66             breadcrumbs.add(screenTitle);
67         }
68         String currentClass = clazz;
69         String currentTitle = screenTitle;
70         // Look up current page's parent, if found add it to breadcrumb string list, and repeat.
71         while (true) {
72             final SiteMapPair pair = lookUpParent(currentClass, currentTitle);
73             if (pair == null) {
74                 if (DEBUG_TIMING) {
75                     Log.d(TAG, "BreadCrumb timing: " + (System.currentTimeMillis() - startTime));
76                 }
77                 return breadcrumbs;
78             }
79             final String parentTitle = pair.getParentTitle();
80             if (!TextUtils.isEmpty(parentTitle)) {
81                 breadcrumbs.add(0, parentTitle);
82             }
83             currentClass = pair.getParentClass();
84             currentTitle = pair.getParentTitle();
85         }
86     }
87 
88     /**
89      * Initialize a list of {@link SiteMapPair}s. Each pair knows about a single parent-child
90      * page relationship.
91      */
92     @WorkerThread
init(Context context)93     private synchronized void init(Context context) {
94         if (mInitialized) {
95             // Make sure only init once.
96             return;
97         }
98         final long startTime = System.currentTimeMillis();
99         // First load site map from static index table.
100         final Context appContext = context.getApplicationContext();
101         final SQLiteDatabase db = IndexDatabaseHelper.getInstance(appContext).getReadableDatabase();
102         Cursor sitemap = db.query(IndexDatabaseHelper.Tables.TABLE_SITE_MAP, SITE_MAP_COLUMNS, null,
103                 null, null, null, null);
104         while (sitemap.moveToNext()) {
105             final SiteMapPair pair = new SiteMapPair(
106                     sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_CLASS)),
107                     sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.PARENT_TITLE)),
108                     sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_CLASS)),
109                     sitemap.getString(sitemap.getColumnIndex(SiteMapColumns.CHILD_TITLE)));
110             mPairs.add(pair);
111         }
112         sitemap.close();
113         // Done.
114         mInitialized = true;
115         if (DEBUG_TIMING) {
116             Log.d(TAG, "Init timing: " + (System.currentTimeMillis() - startTime));
117         }
118     }
119 
120     @WorkerThread
lookUpParent(String clazz, String title)121     private SiteMapPair lookUpParent(String clazz, String title) {
122         for (SiteMapPair pair : mPairs) {
123             if (TextUtils.equals(pair.getChildClass(), clazz)
124                     && TextUtils.equals(title, pair.getChildTitle())) {
125                 return pair;
126             }
127         }
128         return null;
129     }
130 
131 }
132