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