1/*
2 * Copyright (C) 2006 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.content;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21import android.text.TextUtils;
22
23import java.io.PrintWriter;
24import java.lang.Comparable;
25
26/**
27 * Identifier for a specific application component
28 * ({@link android.app.Activity}, {@link android.app.Service},
29 * {@link android.content.BroadcastReceiver}, or
30 * {@link android.content.ContentProvider}) that is available.  Two
31 * pieces of information, encapsulated here, are required to identify
32 * a component: the package (a String) it exists in, and the class (a String)
33 * name inside of that package.
34 *
35 */
36public final class ComponentName implements Parcelable, Cloneable, Comparable<ComponentName> {
37    private final String mPackage;
38    private final String mClass;
39
40    /**
41     * Create a new component identifier where the class name may be specified
42     * as either absolute or relative to the containing package.
43     *
44     * <p>Relative package names begin with a <code>'.'</code> character. For a package
45     * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
46     * will return a ComponentName with the package <code>"com.example"</code>and class name
47     * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
48     * permitted.</p>
49     *
50     * @param pkg the name of the package the component exists in
51     * @param cls the name of the class inside of <var>pkg</var> that implements
52     *            the component
53     * @return the new ComponentName
54     */
55    public static ComponentName createRelative(String pkg, String cls) {
56        if (TextUtils.isEmpty(cls)) {
57            throw new IllegalArgumentException("class name cannot be empty");
58        }
59
60        final String fullName;
61        if (cls.charAt(0) == '.') {
62            // Relative to the package. Prepend the package name.
63            fullName = pkg + cls;
64        } else {
65            // Fully qualified package name.
66            fullName = cls;
67        }
68        return new ComponentName(pkg, fullName);
69    }
70
71    /**
72     * Create a new component identifier where the class name may be specified
73     * as either absolute or relative to the containing package.
74     *
75     * <p>Relative package names begin with a <code>'.'</code> character. For a package
76     * <code>"com.example"</code> and class name <code>".app.MyActivity"</code> this method
77     * will return a ComponentName with the package <code>"com.example"</code>and class name
78     * <code>"com.example.app.MyActivity"</code>. Fully qualified class names are also
79     * permitted.</p>
80     *
81     * @param pkg a Context for the package implementing the component
82     * @param cls the name of the class inside of <var>pkg</var> that implements
83     *            the component
84     * @return the new ComponentName
85     */
86    public static ComponentName createRelative(Context pkg, String cls) {
87        return createRelative(pkg.getPackageName(), cls);
88    }
89
90    /**
91     * Create a new component identifier.
92     *
93     * @param pkg The name of the package that the component exists in.  Can
94     * not be null.
95     * @param cls The name of the class inside of <var>pkg</var> that
96     * implements the component.  Can not be null.
97     */
98    public ComponentName(String pkg, String cls) {
99        if (pkg == null) throw new NullPointerException("package name is null");
100        if (cls == null) throw new NullPointerException("class name is null");
101        mPackage = pkg;
102        mClass = cls;
103    }
104
105    /**
106     * Create a new component identifier from a Context and class name.
107     *
108     * @param pkg A Context for the package implementing the component,
109     * from which the actual package name will be retrieved.
110     * @param cls The name of the class inside of <var>pkg</var> that
111     * implements the component.
112     */
113    public ComponentName(Context pkg, String cls) {
114        if (cls == null) throw new NullPointerException("class name is null");
115        mPackage = pkg.getPackageName();
116        mClass = cls;
117    }
118
119    /**
120     * Create a new component identifier from a Context and Class object.
121     *
122     * @param pkg A Context for the package implementing the component, from
123     * which the actual package name will be retrieved.
124     * @param cls The Class object of the desired component, from which the
125     * actual class name will be retrieved.
126     */
127    public ComponentName(Context pkg, Class<?> cls) {
128        mPackage = pkg.getPackageName();
129        mClass = cls.getName();
130    }
131
132    public ComponentName clone() {
133        return new ComponentName(mPackage, mClass);
134    }
135
136    /**
137     * Return the package name of this component.
138     */
139    public String getPackageName() {
140        return mPackage;
141    }
142
143    /**
144     * Return the class name of this component.
145     */
146    public String getClassName() {
147        return mClass;
148    }
149
150    /**
151     * Return the class name, either fully qualified or in a shortened form
152     * (with a leading '.') if it is a suffix of the package.
153     */
154    public String getShortClassName() {
155        if (mClass.startsWith(mPackage)) {
156            int PN = mPackage.length();
157            int CN = mClass.length();
158            if (CN > PN && mClass.charAt(PN) == '.') {
159                return mClass.substring(PN, CN);
160            }
161        }
162        return mClass;
163    }
164
165    private static void appendShortClassName(StringBuilder sb, String packageName,
166            String className) {
167        if (className.startsWith(packageName)) {
168            int PN = packageName.length();
169            int CN = className.length();
170            if (CN > PN && className.charAt(PN) == '.') {
171                sb.append(className, PN, CN);
172                return;
173            }
174        }
175        sb.append(className);
176    }
177
178    private static void printShortClassName(PrintWriter pw, String packageName,
179            String className) {
180        if (className.startsWith(packageName)) {
181            int PN = packageName.length();
182            int CN = className.length();
183            if (CN > PN && className.charAt(PN) == '.') {
184                pw.write(className, PN, CN-PN);
185                return;
186            }
187        }
188        pw.print(className);
189    }
190
191    /**
192     * Return a String that unambiguously describes both the package and
193     * class names contained in the ComponentName.  You can later recover
194     * the ComponentName from this string through
195     * {@link #unflattenFromString(String)}.
196     *
197     * @return Returns a new String holding the package and class names.  This
198     * is represented as the package name, concatenated with a '/' and then the
199     * class name.
200     *
201     * @see #unflattenFromString(String)
202     */
203    public String flattenToString() {
204        return mPackage + "/" + mClass;
205    }
206
207    /**
208     * The same as {@link #flattenToString()}, but abbreviates the class
209     * name if it is a suffix of the package.  The result can still be used
210     * with {@link #unflattenFromString(String)}.
211     *
212     * @return Returns a new String holding the package and class names.  This
213     * is represented as the package name, concatenated with a '/' and then the
214     * class name.
215     *
216     * @see #unflattenFromString(String)
217     */
218    public String flattenToShortString() {
219        StringBuilder sb = new StringBuilder(mPackage.length() + mClass.length());
220        appendShortString(sb, mPackage, mClass);
221        return sb.toString();
222    }
223
224    /** @hide */
225    public void appendShortString(StringBuilder sb) {
226        appendShortString(sb, mPackage, mClass);
227    }
228
229    /** @hide */
230    public static void appendShortString(StringBuilder sb, String packageName, String className) {
231        sb.append(packageName).append('/');
232        appendShortClassName(sb, packageName, className);
233    }
234
235    /** @hide */
236    public static void printShortString(PrintWriter pw, String packageName, String className) {
237        pw.print(packageName);
238        pw.print('/');
239        printShortClassName(pw, packageName, className);
240    }
241
242    /**
243     * Recover a ComponentName from a String that was previously created with
244     * {@link #flattenToString()}.  It splits the string at the first '/',
245     * taking the part before as the package name and the part after as the
246     * class name.  As a special convenience (to use, for example, when
247     * parsing component names on the command line), if the '/' is immediately
248     * followed by a '.' then the final class name will be the concatenation
249     * of the package name with the string following the '/'.  Thus
250     * "com.foo/.Blah" becomes package="com.foo" class="com.foo.Blah".
251     *
252     * @param str The String that was returned by flattenToString().
253     * @return Returns a new ComponentName containing the package and class
254     * names that were encoded in <var>str</var>
255     *
256     * @see #flattenToString()
257     */
258    public static ComponentName unflattenFromString(String str) {
259        int sep = str.indexOf('/');
260        if (sep < 0 || (sep+1) >= str.length()) {
261            return null;
262        }
263        String pkg = str.substring(0, sep);
264        String cls = str.substring(sep+1);
265        if (cls.length() > 0 && cls.charAt(0) == '.') {
266            cls = pkg + cls;
267        }
268        return new ComponentName(pkg, cls);
269    }
270
271    /**
272     * Return string representation of this class without the class's name
273     * as a prefix.
274     */
275    public String toShortString() {
276        return "{" + mPackage + "/" + mClass + "}";
277    }
278
279    @Override
280    public String toString() {
281        return "ComponentInfo{" + mPackage + "/" + mClass + "}";
282    }
283
284    @Override
285    public boolean equals(Object obj) {
286        try {
287            if (obj != null) {
288                ComponentName other = (ComponentName)obj;
289                // Note: no null checks, because mPackage and mClass can
290                // never be null.
291                return mPackage.equals(other.mPackage)
292                        && mClass.equals(other.mClass);
293            }
294        } catch (ClassCastException e) {
295        }
296        return false;
297    }
298
299    @Override
300    public int hashCode() {
301        return mPackage.hashCode() + mClass.hashCode();
302    }
303
304    public int compareTo(ComponentName that) {
305        int v;
306        v = this.mPackage.compareTo(that.mPackage);
307        if (v != 0) {
308            return v;
309        }
310        return this.mClass.compareTo(that.mClass);
311    }
312
313    public int describeContents() {
314        return 0;
315    }
316
317    public void writeToParcel(Parcel out, int flags) {
318        out.writeString(mPackage);
319        out.writeString(mClass);
320    }
321
322    /**
323     * Write a ComponentName to a Parcel, handling null pointers.  Must be
324     * read with {@link #readFromParcel(Parcel)}.
325     *
326     * @param c The ComponentName to be written.
327     * @param out The Parcel in which the ComponentName will be placed.
328     *
329     * @see #readFromParcel(Parcel)
330     */
331    public static void writeToParcel(ComponentName c, Parcel out) {
332        if (c != null) {
333            c.writeToParcel(out, 0);
334        } else {
335            out.writeString(null);
336        }
337    }
338
339    /**
340     * Read a ComponentName from a Parcel that was previously written
341     * with {@link #writeToParcel(ComponentName, Parcel)}, returning either
342     * a null or new object as appropriate.
343     *
344     * @param in The Parcel from which to read the ComponentName
345     * @return Returns a new ComponentName matching the previously written
346     * object, or null if a null had been written.
347     *
348     * @see #writeToParcel(ComponentName, Parcel)
349     */
350    public static ComponentName readFromParcel(Parcel in) {
351        String pkg = in.readString();
352        return pkg != null ? new ComponentName(pkg, in) : null;
353    }
354
355    public static final Parcelable.Creator<ComponentName> CREATOR
356            = new Parcelable.Creator<ComponentName>() {
357        public ComponentName createFromParcel(Parcel in) {
358            return new ComponentName(in);
359        }
360
361        public ComponentName[] newArray(int size) {
362            return new ComponentName[size];
363        }
364    };
365
366    /**
367     * Instantiate a new ComponentName from the data in a Parcel that was
368     * previously written with {@link #writeToParcel(Parcel, int)}.  Note that you
369     * must not use this with data written by
370     * {@link #writeToParcel(ComponentName, Parcel)} since it is not possible
371     * to handle a null ComponentObject here.
372     *
373     * @param in The Parcel containing the previously written ComponentName,
374     * positioned at the location in the buffer where it was written.
375     */
376    public ComponentName(Parcel in) {
377        mPackage = in.readString();
378        if (mPackage == null) throw new NullPointerException(
379                "package name is null");
380        mClass = in.readString();
381        if (mClass == null) throw new NullPointerException(
382                "class name is null");
383    }
384
385    private ComponentName(String pkg, Parcel in) {
386        mPackage = pkg;
387        mClass = in.readString();
388    }
389}
390