1 /*
2  * Copyright (C) 2008 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 android.media;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.media.IMediaScannerListener;
24 import android.media.IMediaScannerService;
25 import android.net.Uri;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 import android.util.Log;
29 
30 
31 /**
32  * MediaScannerConnection provides a way for applications to pass a
33  * newly created or downloaded media file to the media scanner service.
34  * The media scanner service will read metadata from the file and add
35  * the file to the media content provider.
36  * The MediaScannerConnectionClient provides an interface for the
37  * media scanner service to return the Uri for a newly scanned file
38  * to the client of the MediaScannerConnection class.
39  */
40 public class MediaScannerConnection implements ServiceConnection {
41 
42     private static final String TAG = "MediaScannerConnection";
43 
44     private Context mContext;
45     private MediaScannerConnectionClient mClient;
46     private IMediaScannerService mService;
47     private boolean mConnected; // true if connect() has been called since last disconnect()
48 
49     private final IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() {
50         public void scanCompleted(String path, Uri uri) {
51             MediaScannerConnectionClient client = mClient;
52             if (client != null) {
53                 client.onScanCompleted(path, uri);
54             }
55         }
56     };
57 
58     /**
59      * Interface for notifying clients of the result of scanning a
60      * requested media file.
61      */
62     public interface OnScanCompletedListener {
63         /**
64          * Called to notify the client when the media scanner has finished
65          * scanning a file.
66          * @param path the path to the file that has been scanned.
67          * @param uri the Uri for the file if the scanning operation succeeded
68          * and the file was added to the media database, or null if scanning failed.
69          */
onScanCompleted(String path, Uri uri)70         public void onScanCompleted(String path, Uri uri);
71     }
72 
73     /**
74      * An interface for notifying clients of MediaScannerConnection
75      * when a connection to the MediaScanner service has been established
76      * and when the scanning of a file has completed.
77      */
78     public interface MediaScannerConnectionClient extends OnScanCompletedListener {
79         /**
80          * Called to notify the client when a connection to the
81          * MediaScanner service has been established.
82          */
onMediaScannerConnected()83         public void onMediaScannerConnected();
84 
85         /**
86          * Called to notify the client when the media scanner has finished
87          * scanning a file.
88          * @param path the path to the file that has been scanned.
89          * @param uri the Uri for the file if the scanning operation succeeded
90          * and the file was added to the media database, or null if scanning failed.
91          */
onScanCompleted(String path, Uri uri)92         public void onScanCompleted(String path, Uri uri);
93     }
94 
95     /**
96      * Constructs a new MediaScannerConnection object.
97      * @param context the Context object, required for establishing a connection to
98      * the media scanner service.
99      * @param client an optional object implementing the MediaScannerConnectionClient
100      * interface, for receiving notifications from the media scanner.
101      */
MediaScannerConnection(Context context, MediaScannerConnectionClient client)102     public MediaScannerConnection(Context context, MediaScannerConnectionClient client) {
103         mContext = context;
104         mClient = client;
105     }
106 
107     /**
108      * Initiates a connection to the media scanner service.
109      * {@link MediaScannerConnectionClient#onMediaScannerConnected()}
110      * will be called when the connection is established.
111      */
connect()112     public void connect() {
113         synchronized (this) {
114             if (!mConnected) {
115                 Intent intent = new Intent(IMediaScannerService.class.getName());
116                 intent.setComponent(
117                         new ComponentName("com.android.providers.media",
118                                 "com.android.providers.media.MediaScannerService"));
119                 mContext.bindService(intent, this, Context.BIND_AUTO_CREATE);
120                 mConnected = true;
121             }
122         }
123     }
124 
125     /**
126      * Releases the connection to the media scanner service.
127      */
disconnect()128     public void disconnect() {
129         synchronized (this) {
130             if (mConnected) {
131                 if (false) {
132                     Log.v(TAG, "Disconnecting from Media Scanner");
133                 }
134                 try {
135                     mContext.unbindService(this);
136                     if (mClient instanceof ClientProxy) {
137                         mClient = null;
138                     }
139                     mService = null;
140                 } catch (IllegalArgumentException ex) {
141                     if (false) {
142                         Log.v(TAG, "disconnect failed: " + ex);
143                     }
144                 }
145                 mConnected = false;
146             }
147         }
148     }
149 
150     /**
151      * Returns whether we are connected to the media scanner service
152      * @return true if we are connected, false otherwise
153      */
isConnected()154     public synchronized boolean isConnected() {
155         return (mService != null && mConnected);
156     }
157 
158     /**
159      * Requests the media scanner to scan a file.
160      * Success or failure of the scanning operation cannot be determined until
161      * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called.
162      *
163      * @param path the path to the file to be scanned.
164      * @param mimeType  an optional mimeType for the file.
165      * If mimeType is null, then the mimeType will be inferred from the file extension.
166      */
scanFile(String path, String mimeType)167      public void scanFile(String path, String mimeType) {
168         synchronized (this) {
169             if (mService == null || !mConnected) {
170                 throw new IllegalStateException("not connected to MediaScannerService");
171             }
172             try {
173                 if (false) {
174                     Log.v(TAG, "Scanning file " + path);
175                 }
176                 mService.requestScanFile(path, mimeType, mListener);
177             } catch (RemoteException e) {
178                 if (false) {
179                     Log.d(TAG, "Failed to scan file " + path);
180                 }
181             }
182         }
183     }
184 
185     static class ClientProxy implements MediaScannerConnectionClient {
186         final String[] mPaths;
187         final String[] mMimeTypes;
188         final OnScanCompletedListener mClient;
189         MediaScannerConnection mConnection;
190         int mNextPath;
191 
ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client)192         ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) {
193             mPaths = paths;
194             mMimeTypes = mimeTypes;
195             mClient = client;
196         }
197 
onMediaScannerConnected()198         public void onMediaScannerConnected() {
199             scanNextPath();
200         }
201 
onScanCompleted(String path, Uri uri)202         public void onScanCompleted(String path, Uri uri) {
203             if (mClient != null) {
204                 mClient.onScanCompleted(path, uri);
205             }
206             scanNextPath();
207         }
208 
scanNextPath()209         void scanNextPath() {
210             if (mNextPath >= mPaths.length) {
211                 mConnection.disconnect();
212                 mConnection = null;
213                 return;
214             }
215             String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null;
216             mConnection.scanFile(mPaths[mNextPath], mimeType);
217             mNextPath++;
218         }
219     }
220 
221     /**
222      * Convenience for constructing a {@link MediaScannerConnection}, calling
223      * {@link #connect} on it, and calling {@link #scanFile(String, String)} with the given
224      * <var>path</var> and <var>mimeType</var> when the connection is
225      * established.
226      * @param context The caller's Context, required for establishing a connection to
227      * the media scanner service.
228      * Success or failure of the scanning operation cannot be determined until
229      * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called.
230      * @param paths Array of paths to be scanned.
231      * @param mimeTypes Optional array of MIME types for each path.
232      * If mimeType is null, then the mimeType will be inferred from the file extension.
233      * @param callback Optional callback through which you can receive the
234      * scanned URI and MIME type; If null, the file will be scanned but
235      * you will not get a result back.
236      * @see #scanFile(String, String)
237      */
scanFile(Context context, String[] paths, String[] mimeTypes, OnScanCompletedListener callback)238     public static void scanFile(Context context, String[] paths, String[] mimeTypes,
239             OnScanCompletedListener callback) {
240         ClientProxy client = new ClientProxy(paths, mimeTypes, callback);
241         MediaScannerConnection connection = new MediaScannerConnection(context, client);
242         client.mConnection = connection;
243         connection.connect();
244     }
245 
246     /**
247      * Part of the ServiceConnection interface.  Do not call.
248      */
onServiceConnected(ComponentName className, IBinder service)249     public void onServiceConnected(ComponentName className, IBinder service) {
250         if (false) {
251             Log.v(TAG, "Connected to Media Scanner");
252         }
253         synchronized (this) {
254             mService = IMediaScannerService.Stub.asInterface(service);
255             if (mService != null && mClient != null) {
256                 mClient.onMediaScannerConnected();
257             }
258         }
259     }
260 
261     /**
262      * Part of the ServiceConnection interface.  Do not call.
263      */
onServiceDisconnected(ComponentName className)264     public void onServiceDisconnected(ComponentName className) {
265         if (false) {
266             Log.v(TAG, "Disconnected from Media Scanner");
267         }
268         synchronized (this) {
269             mService = null;
270         }
271     }
272 }
273