1  /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 package org.mozilla.gecko.home;
7 
8 import java.util.EnumSet;
9 import java.util.List;
10 
11 import android.util.Log;
12 import org.mozilla.gecko.R;
13 import org.mozilla.gecko.Telemetry;
14 import org.mozilla.gecko.TelemetryContract;
15 import org.mozilla.gecko.db.BrowserContract;
16 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
17 import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
18 
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.util.AttributeSet;
22 import android.view.KeyEvent;
23 import android.view.View;
24 import android.widget.AdapterView;
25 import android.widget.HeaderViewListAdapter;
26 import android.widget.ListAdapter;
27 
28 import org.mozilla.gecko.reader.SavedReaderViewHelper;
29 import org.mozilla.gecko.util.NetworkUtils;
30 
31 /**
32  * A ListView of bookmarks.
33  */
34 public class BookmarksListView extends HomeListView
35                                implements AdapterView.OnItemClickListener {
36     public static final String LOGTAG = "GeckoBookmarksListView";
37 
BookmarksListView(Context context)38     public BookmarksListView(Context context) {
39         this(context, null);
40     }
41 
BookmarksListView(Context context, AttributeSet attrs)42     public BookmarksListView(Context context, AttributeSet attrs) {
43         this(context, attrs, R.attr.bookmarksListViewStyle);
44     }
45 
BookmarksListView(Context context, AttributeSet attrs, int defStyle)46     public BookmarksListView(Context context, AttributeSet attrs, int defStyle) {
47         super(context, attrs, defStyle);
48     }
49 
50     @Override
onAttachedToWindow()51     public void onAttachedToWindow() {
52         super.onAttachedToWindow();
53 
54         setOnItemClickListener(this);
55 
56         setOnKeyListener(new View.OnKeyListener() {
57             @Override
58             public boolean onKey(View v, int keyCode, KeyEvent event) {
59                 final int action = event.getAction();
60 
61                 // If the user hit the BACK key, try to move to the parent folder.
62                 if (action == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
63                     return getBookmarksListAdapter().moveToParentFolder();
64                 }
65                 return false;
66             }
67         });
68     }
69 
70     /**
71      * Get the appropriate telemetry extra for a given folder.
72      *
73      * baseFolderID is the ID of the first-level folder in the parent stack, i.e. the first folder
74      * that was selected from the root hierarchy (e.g. Desktop, Reading List, or any mobile first-level
75      * subfolder). If the current folder is a first-level folder, then the fixed root ID may be used
76      * instead.
77      *
78      * We use baseFolderID only to distinguish whether or not we're currently in a desktop subfolder.
79      * If it isn't equal to FAKE_DESKTOP_FOLDER_ID we know we're in a mobile subfolder, or one
80      * of the smartfolders.
81      */
getTelemetryExtraForFolder(int folderID, int baseFolderID)82     private String getTelemetryExtraForFolder(int folderID, int baseFolderID) {
83         if (folderID == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
84             return "folder_desktop";
85         } else if (folderID == Bookmarks.FIXED_SCREENSHOT_FOLDER_ID) {
86             return "folder_screenshots";
87         } else if (folderID == Bookmarks.FAKE_READINGLIST_SMARTFOLDER_ID) {
88             return "folder_reading_list";
89         } else {
90             // The stack depth is 2 for either the fake desktop folder, or any subfolder of mobile
91             // bookmarks, we subtract these offsets so that any direct subfolder of mobile
92             // has a level equal to 1. (Desktop folders will be one level deeper due to the
93             // fake desktop folder, hence subtract 2.)
94             if (baseFolderID == Bookmarks.FAKE_DESKTOP_FOLDER_ID) {
95                 return "folder_desktop_subfolder";
96             } else {
97                 return "folder_mobile_subfolder";
98             }
99         }
100     }
101 
102     @Override
onItemClick(AdapterView<?> parent, View view, int position, long id)103     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
104         final BookmarksListAdapter adapter = getBookmarksListAdapter();
105         if (adapter.isShowingChildFolder()) {
106             if (position == 0) {
107                 // If we tap on an opened folder, move back to parent folder.
108 
109                 final List<BookmarksListAdapter.FolderInfo> parentStack = ((BookmarksListAdapter) getAdapter()).getParentStack();
110                 if (parentStack.size() < 2) {
111                     throw new IllegalStateException("Cannot move to parent folder if we are already in the root folder");
112                 }
113 
114                 // The first item (top of stack) is the current folder, we're returning to the next one
115                 BookmarksListAdapter.FolderInfo folder = parentStack.get(1);
116                 final int parentID = folder.id;
117                 final int baseFolderID;
118                 if (parentStack.size() > 2) {
119                     baseFolderID = parentStack.get(parentStack.size() - 2).id;
120                 } else {
121                     baseFolderID = Bookmarks.FIXED_ROOT_ID;
122                 }
123 
124                 final String extra = getTelemetryExtraForFolder(parentID, baseFolderID);
125 
126                 // Move to parent _after_ retrieving stack information
127                 adapter.moveToParentFolder();
128 
129                 Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.LIST_ITEM, extra);
130                 return;
131             }
132 
133             // Accounting for the folder view.
134             position--;
135         }
136 
137         final Cursor cursor = adapter.getCursor();
138         if (cursor == null) {
139             return;
140         }
141 
142         cursor.moveToPosition(position);
143 
144         if (adapter.getOpenFolderType() == BookmarksListAdapter.FolderType.SCREENSHOTS) {
145             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "bookmarks-screenshot");
146 
147             final String fileUrl = "file://" + cursor.getString(cursor.getColumnIndex(BrowserContract.UrlAnnotations.VALUE));
148             getOnUrlOpenListener().onUrlOpen(fileUrl, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
149             return;
150         }
151 
152         int type = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks.TYPE));
153         if (type == Bookmarks.TYPE_FOLDER) {
154             // If we're clicking on a folder, update adapter to move to that folder
155             final int folderId = cursor.getInt(cursor.getColumnIndexOrThrow(Bookmarks._ID));
156             final String folderTitle = adapter.getFolderTitle(parent.getContext(), cursor);
157             adapter.moveToChildFolder(folderId, folderTitle);
158 
159             final List<BookmarksListAdapter.FolderInfo> parentStack = ((BookmarksListAdapter) getAdapter()).getParentStack();
160 
161             final int baseFolderID;
162             if (parentStack.size() > 2) {
163                 baseFolderID = parentStack.get(parentStack.size() - 2).id;
164             } else {
165                 baseFolderID = Bookmarks.FIXED_ROOT_ID;
166             }
167 
168             final String extra = getTelemetryExtraForFolder(folderId, baseFolderID);
169             Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.LIST_ITEM, extra);
170         } else {
171             // Otherwise, just open the URL
172             final String url = cursor.getString(cursor.getColumnIndexOrThrow(Bookmarks.URL));
173 
174             final SavedReaderViewHelper rvh = SavedReaderViewHelper.getSavedReaderViewHelper(getContext());
175 
176             final String extra;
177             if (rvh.isURLCached(url)) {
178                 extra = "bookmarks-reader";
179             } else {
180                 extra = "bookmarks";
181             }
182 
183             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, extra);
184             Telemetry.addToHistogram("FENNEC_LOAD_SAVED_PAGE", NetworkUtils.isConnected(getContext()) ? 2 : 3);
185 
186             // This item is a TwoLinePageRow, so we allow switch-to-tab.
187             getOnUrlOpenListener().onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
188         }
189     }
190 
191     @Override
onItemLongClick(AdapterView<?> parent, View view, int position, long id)192     public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
193         // Adjust the item position to account for the parent folder row that is inserted
194         // at the top of the list when viewing the contents of a folder.
195         final BookmarksListAdapter adapter = getBookmarksListAdapter();
196         if (adapter.isShowingChildFolder()) {
197             position--;
198         }
199 
200         // Temporarily prevent crashes until we figure out what we actually want to do here (bug 1252316).
201         if (adapter.getOpenFolderType() == BookmarksListAdapter.FolderType.SCREENSHOTS) {
202             return false;
203         }
204 
205         return super.onItemLongClick(parent, view, position, id);
206     }
207 
getBookmarksListAdapter()208     private BookmarksListAdapter getBookmarksListAdapter() {
209         BookmarksListAdapter adapter;
210         ListAdapter listAdapter = getAdapter();
211         if (listAdapter instanceof HeaderViewListAdapter) {
212             adapter = (BookmarksListAdapter) ((HeaderViewListAdapter) listAdapter).getWrappedAdapter();
213         } else {
214             adapter = (BookmarksListAdapter) listAdapter;
215         }
216         return adapter;
217     }
218 }
219