QuickViewIntentBuilder.java revision 32a34d337977618d95ab183f65627a4ff4b9a390
1/* 2 * Copyright (C) 2015 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 17package com.android.documentsui; 18 19import static com.android.documentsui.Shared.DEBUG; 20import static com.android.documentsui.Shared.TAG; 21import static com.android.documentsui.model.DocumentInfo.getCursorString; 22 23import android.content.ClipData; 24import android.content.ClipDescription; 25import android.content.ComponentName; 26import android.content.Intent; 27import android.content.pm.PackageManager; 28import android.content.res.Resources; 29import android.database.Cursor; 30import android.net.Uri; 31import android.provider.DocumentsContract; 32import android.provider.DocumentsContract.Document; 33import android.support.annotation.Nullable; 34import android.text.TextUtils; 35import android.util.Log; 36import android.util.Range; 37 38import com.android.documentsui.dirlist.Model; 39import com.android.documentsui.model.DocumentInfo; 40 41import java.util.ArrayList; 42import java.util.List; 43 44/** 45 * Provides support for gather a list of quick-viewable files into a quick view intent. 46 */ 47final class QuickViewIntentBuilder { 48 private static final int MAX_CLIP_ITEMS = 1000; 49 50 private final DocumentInfo mDocument; 51 private final Model mModel; 52 53 private final PackageManager mPkgManager; 54 private final Resources mResources; 55 56 public QuickViewIntentBuilder( 57 PackageManager pkgManager, 58 Resources resources, 59 DocumentInfo doc, 60 Model model) { 61 62 mPkgManager = pkgManager; 63 mResources = resources; 64 mDocument = doc; 65 mModel = model; 66 } 67 68 /** 69 * Builds the intent for quick viewing. Short circuits building if a handler cannot 70 * be resolved; in this case {@code null} is returned. 71 */ 72 @Nullable Intent build() { 73 if (DEBUG) Log.d(TAG, "Preparing intent for doc:" + mDocument.documentId); 74 75 String trustedPkg = mResources.getString(R.string.trusted_quick_viewer_package); 76 77 if (!TextUtils.isEmpty(trustedPkg)) { 78 Intent intent = new Intent(Intent.ACTION_QUICK_VIEW); 79 intent.setDataAndType(mDocument.derivedUri, mDocument.mimeType); 80 intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 81 intent.setPackage(trustedPkg); 82 if (hasRegisteredHandler(intent)) { 83 final ArrayList<Uri> uris = new ArrayList<Uri>(); 84 final int documentLocation = collectViewableUris(uris); 85 final Range<Integer> range = computeSiblingsRange(uris, documentLocation); 86 87 ClipData clipData = null; 88 ClipData.Item item; 89 Uri uri; 90 for (int i = range.getLower(); i <= range.getUpper(); i++) { 91 uri = uris.get(i); 92 item = new ClipData.Item(uri); 93 if (DEBUG) Log.d(TAG, "Including file: " + uri); 94 if (clipData == null) { 95 clipData = new ClipData( 96 "URIs", new String[] { ClipDescription.MIMETYPE_TEXT_URILIST }, 97 item); 98 } else { 99 clipData.addItem(item); 100 } 101 } 102 103 intent.putExtra(Intent.EXTRA_INDEX, documentLocation); 104 intent.setClipData(clipData); 105 106 return intent; 107 } else { 108 Log.e(TAG, "Can't resolve trusted quick view package: " + trustedPkg); 109 } 110 } 111 112 return null; 113 } 114 115 private int collectViewableUris(ArrayList<Uri> uris) { 116 final List<String> siblingIds = mModel.getModelIds(); 117 uris.ensureCapacity(siblingIds.size()); 118 119 int documentLocation = 0; 120 Cursor cursor; 121 String mimeType; 122 String id; 123 String authority; 124 Uri uri; 125 126 // Cursor's are not guaranteed to be immutable. Hence, traverse it only once. 127 for (int i = 0; i < siblingIds.size(); i++) { 128 cursor = mModel.getItem(siblingIds.get(i)); 129 130 mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); 131 if (Document.MIME_TYPE_DIR.equals(mimeType)) { 132 continue; 133 } 134 135 id = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); 136 authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY); 137 uri = DocumentsContract.buildDocumentUri(authority, id); 138 139 if (id.equals(mDocument.documentId)) { 140 if (DEBUG) Log.d(TAG, "Found starting point for QV. " + i); 141 documentLocation = i; 142 } 143 144 uris.add(uri); 145 } 146 147 return documentLocation; 148 } 149 150 private static Range<Integer> computeSiblingsRange(List<Uri> uris, int documentLocation) { 151 // Restrict number of siblings to avoid hitting the IPC limit. 152 // TODO: Remove this restriction once ClipData can hold an arbitrary number of 153 // items. 154 int firstSibling; 155 int lastSibling; 156 if (documentLocation < uris.size() / 2) { 157 firstSibling = Math.max(0, documentLocation - MAX_CLIP_ITEMS / 2); 158 lastSibling = Math.min(uris.size() - 1, firstSibling + MAX_CLIP_ITEMS - 1); 159 } else { 160 lastSibling = Math.min(uris.size() - 1, documentLocation + MAX_CLIP_ITEMS / 2); 161 firstSibling = Math.max(0, lastSibling - MAX_CLIP_ITEMS + 1); 162 } 163 164 if (DEBUG) Log.d(TAG, "Copmuted siblings from index: " + firstSibling 165 + " to: " + lastSibling); 166 167 return new Range(firstSibling, lastSibling); 168 } 169 170 private boolean hasRegisteredHandler(Intent intent) { 171 // Try to resolve the intent. If a matching app isn't installed, it won't resolve. 172 return intent.resolveActivity(mPkgManager) != null; 173 } 174} 175