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
17package android.media;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.media.IMediaScannerListener;
24import android.media.IMediaScannerService;
25import android.net.Uri;
26import android.os.IBinder;
27import android.os.RemoteException;
28import 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 */
40public 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         */
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         */
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         */
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     */
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     */
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     */
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     */
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     */
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
192        ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) {
193            mPaths = paths;
194            mMimeTypes = mimeTypes;
195            mClient = client;
196        }
197
198        public void onMediaScannerConnected() {
199            scanNextPath();
200        }
201
202        public void onScanCompleted(String path, Uri uri) {
203            if (mClient != null) {
204                mClient.onScanCompleted(path, uri);
205            }
206            scanNextPath();
207        }
208
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} 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     */
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     */
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     */
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