1925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey/* 2925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * Copyright (C) 2013 The Android Open Source Project 3925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * 4925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License"); 5925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * you may not use this file except in compliance with the License. 6925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * You may obtain a copy of the License at 7925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * 8925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * http://www.apache.org/licenses/LICENSE-2.0 9925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * 10925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * Unless required by applicable law or agreed to in writing, software 11925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS, 12925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * See the License for the specific language governing permissions and 14925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * limitations under the License. 15925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey */ 16925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 17925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeypackage com.android.providers.downloads; 18925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 19925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport static android.text.format.DateUtils.MINUTE_IN_MILLIS; 20925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport static com.android.providers.downloads.Constants.LOGV; 21925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport static com.android.providers.downloads.Constants.TAG; 22925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 23925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport android.content.ContentResolver; 24925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport android.content.ContentUris; 25925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport android.content.ContentValues; 26925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport android.content.Context; 27925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport android.media.MediaScannerConnection; 28925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport android.media.MediaScannerConnection.MediaScannerConnectionClient; 29925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport android.net.Uri; 30925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport android.os.SystemClock; 31925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport android.provider.Downloads; 329b731a5521f569c91aeb419d43fa098a34cf78cbDoug Zongkerimport android.util.Log; 33925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 34925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport com.android.internal.annotations.GuardedBy; 35925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport com.google.common.collect.Maps; 36925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 37925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeyimport java.util.HashMap; 38925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 39925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey/** 40925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * Manages asynchronous scanning of completed downloads. 41925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey */ 42925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeypublic class DownloadScanner implements MediaScannerConnectionClient { 43925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private static final long SCAN_TIMEOUT = MINUTE_IN_MILLIS; 44925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 45925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private final Context mContext; 46925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private final MediaScannerConnection mConnection; 47925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 48925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private static class ScanRequest { 49925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public final long id; 50925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public final String path; 51925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public final String mimeType; 52925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public final long requestRealtime; 53925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 54925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public ScanRequest(long id, String path, String mimeType) { 55925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey this.id = id; 56925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey this.path = path; 57925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey this.mimeType = mimeType; 58925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey this.requestRealtime = SystemClock.elapsedRealtime(); 59925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 60925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 61925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public void exec(MediaScannerConnection conn) { 62925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey conn.scanFile(path, mimeType); 63925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 64925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 65925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 66925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey @GuardedBy("mConnection") 67925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private HashMap<String, ScanRequest> mPending = Maps.newHashMap(); 68925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 69925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public DownloadScanner(Context context) { 70925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mContext = context; 71925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mConnection = new MediaScannerConnection(context, this); 72925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 73925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 74925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey /** 75925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * Check if requested scans are still pending. Scans may timeout after an 76925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * internal duration. 77925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey */ 78925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public boolean hasPendingScans() { 79925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey synchronized (mConnection) { 80925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (mPending.isEmpty()) { 81925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey return false; 82925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } else { 83925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // Check if pending scans have timed out 84925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final long nowRealtime = SystemClock.elapsedRealtime(); 85925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey for (ScanRequest req : mPending.values()) { 86925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (nowRealtime < req.requestRealtime + SCAN_TIMEOUT) { 87925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey return true; 88925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 89925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 90925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey return false; 91925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 92925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 93925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 94925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 95925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey /** 96925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * Request that given {@link DownloadInfo} be scanned at some point in 97925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * future. Enqueues the request to be scanned asynchronously. 98925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * 99925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * @see #hasPendingScans() 100925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey */ 101925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public void requestScan(DownloadInfo info) { 1029b731a5521f569c91aeb419d43fa098a34cf78cbDoug Zongker if (LOGV) Log.v(TAG, "requestScan() for " + info.mFileName); 103925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey synchronized (mConnection) { 104925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final ScanRequest req = new ScanRequest(info.mId, info.mFileName, info.mMimeType); 105925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mPending.put(req.path, req); 106925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 107925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (mConnection.isConnected()) { 108925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey req.exec(mConnection); 109925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } else { 110925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mConnection.connect(); 111925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 112925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 113925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 114925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 115925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public void shutdown() { 116925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mConnection.disconnect(); 117925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 118925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 119925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey @Override 120925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public void onMediaScannerConnected() { 121925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey synchronized (mConnection) { 122925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey for (ScanRequest req : mPending.values()) { 123925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey req.exec(mConnection); 124925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 125925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 126925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 127925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 128925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey @Override 129925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public void onScanCompleted(String path, Uri uri) { 130925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final ScanRequest req; 131925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey synchronized (mConnection) { 132925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey req = mPending.remove(path); 133925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 134925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (req == null) { 1359b731a5521f569c91aeb419d43fa098a34cf78cbDoug Zongker Log.w(TAG, "Missing request for path " + path); 136925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey return; 137925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 138925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 139925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // Update scanned column, which will kick off a database update pass, 140925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // eventually deciding if overall service is ready for teardown. 141925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final ContentValues values = new ContentValues(); 142925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, 1); 143925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (uri != null) { 144925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey values.put(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, uri.toString()); 145925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 146925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 147925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final ContentResolver resolver = mContext.getContentResolver(); 148925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final Uri downloadUri = ContentUris.withAppendedId( 149925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, req.id); 150925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final int rows = resolver.update(downloadUri, values, null, null); 151925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (rows == 0) { 152925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // Local row disappeared during scan; download was probably deleted 153925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // so clean up now-orphaned media entry. 154925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey resolver.delete(uri, null, null); 155925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 156925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 157925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey} 158