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; 383a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkeyimport java.util.concurrent.CountDownLatch; 393a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkeyimport java.util.concurrent.TimeUnit; 40925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 41925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey/** 42925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * Manages asynchronous scanning of completed downloads. 43925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey */ 44925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkeypublic class DownloadScanner implements MediaScannerConnectionClient { 45925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private static final long SCAN_TIMEOUT = MINUTE_IN_MILLIS; 46925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 47925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private final Context mContext; 48925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private final MediaScannerConnection mConnection; 49925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 50925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private static class ScanRequest { 51925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public final long id; 52925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public final String path; 53925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public final String mimeType; 54925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public final long requestRealtime; 55925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 56925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public ScanRequest(long id, String path, String mimeType) { 57925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey this.id = id; 58925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey this.path = path; 59925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey this.mimeType = mimeType; 60925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey this.requestRealtime = SystemClock.elapsedRealtime(); 61925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 62925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 63925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public void exec(MediaScannerConnection conn) { 64925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey conn.scanFile(path, mimeType); 65925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 66925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 67925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 68925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey @GuardedBy("mConnection") 69925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey private HashMap<String, ScanRequest> mPending = Maps.newHashMap(); 70925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 713a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey private CountDownLatch mLatch; 723a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey 73925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public DownloadScanner(Context context) { 74925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mContext = context; 75925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mConnection = new MediaScannerConnection(context, this); 76925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 77925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 783a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey public static void requestScanBlocking(Context context, DownloadInfo info) { 79053674aa4d0ecd7e37dadb8ee37a9cafbdf2f16cJeff Sharkey requestScanBlocking(context, info.mId, info.mFileName, info.mMimeType); 80053674aa4d0ecd7e37dadb8ee37a9cafbdf2f16cJeff Sharkey } 81053674aa4d0ecd7e37dadb8ee37a9cafbdf2f16cJeff Sharkey 82053674aa4d0ecd7e37dadb8ee37a9cafbdf2f16cJeff Sharkey public static void requestScanBlocking(Context context, long id, String path, String mimeType) { 833a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey final DownloadScanner scanner = new DownloadScanner(context); 843a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey scanner.mLatch = new CountDownLatch(1); 85053674aa4d0ecd7e37dadb8ee37a9cafbdf2f16cJeff Sharkey scanner.requestScan(new ScanRequest(id, path, mimeType)); 863a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey try { 873a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey scanner.mLatch.await(SCAN_TIMEOUT, TimeUnit.MILLISECONDS); 883a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey } catch (InterruptedException e) { 893a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey Thread.currentThread().interrupt(); 903a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey } finally { 913a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey scanner.shutdown(); 923a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey } 933a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey } 943a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey 95925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey /** 96925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * Check if requested scans are still pending. Scans may timeout after an 97925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * internal duration. 98925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey */ 99925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public boolean hasPendingScans() { 100925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey synchronized (mConnection) { 101925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (mPending.isEmpty()) { 102925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey return false; 103925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } else { 104925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // Check if pending scans have timed out 105925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final long nowRealtime = SystemClock.elapsedRealtime(); 106925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey for (ScanRequest req : mPending.values()) { 107925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (nowRealtime < req.requestRealtime + SCAN_TIMEOUT) { 108925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey return true; 109925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 110925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 111925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey return false; 112925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 113925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 114925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 115925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 116925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey /** 117925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * Request that given {@link DownloadInfo} be scanned at some point in 118925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * future. Enqueues the request to be scanned asynchronously. 119925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * 120925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey * @see #hasPendingScans() 121925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey */ 122053674aa4d0ecd7e37dadb8ee37a9cafbdf2f16cJeff Sharkey public void requestScan(ScanRequest req) { 123053674aa4d0ecd7e37dadb8ee37a9cafbdf2f16cJeff Sharkey if (LOGV) Log.v(TAG, "requestScan() for " + req.path); 124925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey synchronized (mConnection) { 125925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mPending.put(req.path, req); 126925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 127925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (mConnection.isConnected()) { 128925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey req.exec(mConnection); 129925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } else { 130925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mConnection.connect(); 131925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 132925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 133925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 134925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 135925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public void shutdown() { 136925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey mConnection.disconnect(); 137925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 138925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 139925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey @Override 140925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public void onMediaScannerConnected() { 141925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey synchronized (mConnection) { 142925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey for (ScanRequest req : mPending.values()) { 143925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey req.exec(mConnection); 144925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 145925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 146925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 147925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 148925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey @Override 149925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey public void onScanCompleted(String path, Uri uri) { 150925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final ScanRequest req; 151925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey synchronized (mConnection) { 152925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey req = mPending.remove(path); 153925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 154925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (req == null) { 1559b731a5521f569c91aeb419d43fa098a34cf78cbDoug Zongker Log.w(TAG, "Missing request for path " + path); 156925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey return; 157925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 158925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 159925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // Update scanned column, which will kick off a database update pass, 160925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // eventually deciding if overall service is ready for teardown. 161925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final ContentValues values = new ContentValues(); 162925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, 1); 163925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (uri != null) { 164925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey values.put(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, uri.toString()); 165925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 166925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey 167925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final ContentResolver resolver = mContext.getContentResolver(); 168925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final Uri downloadUri = ContentUris.withAppendedId( 169925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, req.id); 170925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey final int rows = resolver.update(downloadUri, values, null, null); 171925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey if (rows == 0) { 172925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // Local row disappeared during scan; download was probably deleted 173925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey // so clean up now-orphaned media entry. 174925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey resolver.delete(uri, null, null); 175925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 1763a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey 1773a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey if (mLatch != null) { 1783a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey mLatch.countDown(); 1793a5f5eafb34eaa4963c801882148e8f61514a61bJeff Sharkey } 180925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey } 181925976230936a5177365dc24b50da8607a9af8d4Jeff Sharkey} 182