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                } 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     */
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     */
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
188        ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) {
189            mPaths = paths;
190            mMimeTypes = mimeTypes;
191            mClient = client;
192        }
193
194        public void onMediaScannerConnected() {
195            scanNextPath();
196        }
197
198        public void onScanCompleted(String path, Uri uri) {
199            if (mClient != null) {
200                mClient.onScanCompleted(path, uri);
201            }
202            scanNextPath();
203        }
204
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     */
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     */
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     */
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