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