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                 } catch (IllegalArgumentException ex) {
137                     if (false) {
138                         Log.v(TAG, "disconnect failed: " + ex);
139                     }
140                 }
141                 mConnected = false;
142             }
143         }
144     }
145 
146     /**
147      * Returns whether we are connected to the media scanner service
148      * @return true if we are connected, false otherwise
149      */
isConnected()150     public synchronized boolean isConnected() {
151         return (mService != null && mConnected);
152     }
153 
154     /**
155      * Requests the media scanner to scan a file.
156      * Success or failure of the scanning operation cannot be determined until
157      * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called.
158      *
159      * @param path the path to the file to be scanned.
160      * @param mimeType  an optional mimeType for the file.
161      * If mimeType is null, then the mimeType will be inferred from the file extension.
162      */
scanFile(String path, String mimeType)163      public void scanFile(String path, String mimeType) {
164         synchronized (this) {
165             if (mService == null || !mConnected) {
166                 throw new IllegalStateException("not connected to MediaScannerService");
167             }
168             try {
169                 if (false) {
170                     Log.v(TAG, "Scanning file " + path);
171                 }
172                 mService.requestScanFile(path, mimeType, mListener);
173             } catch (RemoteException e) {
174                 if (false) {
175                     Log.d(TAG, "Failed to scan file " + path);
176                 }
177             }
178         }
179     }
180 
181     static class ClientProxy implements MediaScannerConnectionClient {
182         final String[] mPaths;
183         final String[] mMimeTypes;
184         final OnScanCompletedListener mClient;
185         MediaScannerConnection mConnection;
186         int mNextPath;
187 
ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client)188         ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) {
189             mPaths = paths;
190             mMimeTypes = mimeTypes;
191             mClient = client;
192         }
193 
onMediaScannerConnected()194         public void onMediaScannerConnected() {
195             scanNextPath();
196         }
197 
onScanCompleted(String path, Uri uri)198         public void onScanCompleted(String path, Uri uri) {
199             if (mClient != null) {
200                 mClient.onScanCompleted(path, uri);
201             }
202             scanNextPath();
203         }
204 
scanNextPath()205         void scanNextPath() {
206             if (mNextPath >= mPaths.length) {
207                 mConnection.disconnect();
208                 return;
209             }
210             String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null;
211             mConnection.scanFile(mPaths[mNextPath], mimeType);
212             mNextPath++;
213         }
214     }
215 
216     /**
217      * Convenience for constructing a {@link MediaScannerConnection}, calling
218      * {@link #connect} on it, and calling {@link #scanFile} with the given
219      * <var>path</var> and <var>mimeType</var> when the connection is
220      * established.
221      * @param context The caller's Context, required for establishing a connection to
222      * the media scanner service.
223      * Success or failure of the scanning operation cannot be determined until
224      * {@link MediaScannerConnectionClient#onScanCompleted(String, Uri)} is called.
225      * @param paths Array of paths to be scanned.
226      * @param mimeTypes Optional array of MIME types for each path.
227      * If mimeType is null, then the mimeType will be inferred from the file extension.
228      * @param callback Optional callback through which you can receive the
229      * scanned URI and MIME type; If null, the file will be scanned but
230      * you will not get a result back.
231      * @see #scanFile(String, String)
232      */
scanFile(Context context, String[] paths, String[] mimeTypes, OnScanCompletedListener callback)233     public static void scanFile(Context context, String[] paths, String[] mimeTypes,
234             OnScanCompletedListener callback) {
235         ClientProxy client = new ClientProxy(paths, mimeTypes, callback);
236         MediaScannerConnection connection = new MediaScannerConnection(context, client);
237         client.mConnection = connection;
238         connection.connect();
239     }
240 
241     /**
242      * Part of the ServiceConnection interface.  Do not call.
243      */
onServiceConnected(ComponentName className, IBinder service)244     public void onServiceConnected(ComponentName className, IBinder service) {
245         if (false) {
246             Log.v(TAG, "Connected to Media Scanner");
247         }
248         synchronized (this) {
249             mService = IMediaScannerService.Stub.asInterface(service);
250             if (mService != null && mClient != null) {
251                 mClient.onMediaScannerConnected();
252             }
253         }
254     }
255 
256     /**
257      * Part of the ServiceConnection interface.  Do not call.
258      */
onServiceDisconnected(ComponentName className)259     public void onServiceDisconnected(ComponentName className) {
260         if (false) {
261             Log.v(TAG, "Disconnected from Media Scanner");
262         }
263         synchronized (this) {
264             mService = null;
265         }
266     }
267 }
268